Commit 2ef866db3c00e5b1617cff7b3774bf5addcd4ca1

Authored by m-holger
Committed by GitHub
2 parents 5ee0e171 efd5aced

Merge pull request #1565 from m-holger/writer

Refactor QPDFWriter configuration
include/qpdf/QPDFJob.hh
... ... @@ -490,7 +490,7 @@ class QPDFJob
490 490  
491 491 // Output generation
492 492 void doSplitPages(QPDF& pdf);
493   - void setWriterOptions(QPDFWriter&);
  493 + void setWriterOptions(qpdf::Writer&);
494 494 void setEncryptionOptions(QPDFWriter&);
495 495 void maybeFixWritePassword(int R, std::string& password);
496 496 void writeOutfile(QPDF& pdf);
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -43,6 +43,11 @@
43 43 #include <string_view>
44 44 #include <vector>
45 45  
  46 +namespace qpdf
  47 +{
  48 + class Writer;
  49 +}
  50 +
46 51 class QPDF;
47 52  
48 53 // This class implements a simple writer for saving QPDF objects to new PDF files. See comments
... ... @@ -440,6 +445,8 @@ class QPDFWriter
440 445 class NewObjTable;
441 446  
442 447 private:
  448 + friend class qpdf::Writer;
  449 +
443 450 class Members;
444 451  
445 452 std::shared_ptr<Members> m;
... ...
libqpdf/QPDF.cc
... ... @@ -27,9 +27,8 @@
27 27 using namespace qpdf;
28 28 using namespace std::literals;
29 29  
30   -using QDoc = QPDF::Doc;
31   -using Common = QDoc::Common;
32   -using Objects = QDoc::Objects;
  30 +using Common = impl::Doc::Common;
  31 +using Objects = impl::Doc::Objects;
33 32 using Foreign = Objects::Foreign;
34 33 using Streams = Objects::Streams;
35 34  
... ... @@ -724,11 +723,11 @@ QPDF::getRoot()
724 723 std::map<QPDFObjGen, QPDFXRefEntry>
725 724 QPDF::getXRefTable()
726 725 {
727   - return m->objects.getXRefTableInternal();
  726 + return m->objects.xref_table();
728 727 }
729 728  
730 729 std::map<QPDFObjGen, QPDFXRefEntry> const&
731   -Objects::getXRefTableInternal()
  730 +Objects::xref_table()
732 731 {
733 732 if (!m->parsed) {
734 733 throw std::logic_error("QPDF::getXRefTable called before parsing.");
... ...
libqpdf/QPDFJob.cc
... ... @@ -20,7 +20,7 @@
20 20 #include <qpdf/QPDFPageObjectHelper.hh>
21 21 #include <qpdf/QPDFSystemError.hh>
22 22 #include <qpdf/QPDFUsage.hh>
23   -#include <qpdf/QPDFWriter.hh>
  23 +#include <qpdf/QPDFWriter_private.hh>
24 24 #include <qpdf/QPDF_private.hh>
25 25 #include <qpdf/QTC.hh>
26 26 #include <qpdf/QUtil.hh>
... ... @@ -30,8 +30,7 @@
30 30  
31 31 using namespace qpdf;
32 32  
33   -using QDoc = QPDF::Doc;
34   -using Pages = QDoc::Pages;
  33 +using Pages = impl::Doc::Pages;
35 34  
36 35 namespace
37 36 {
... ... @@ -477,7 +476,7 @@ QPDFJob::writeQPDF(QPDF&amp; pdf)
477 476 if (!pdf.getWarnings().empty()) {
478 477 m->warnings = true;
479 478 }
480   - if (m->warnings && !m->qcf.suppress_warnings()) {
  479 + if (m->warnings && !m->d_cfg.suppress_warnings()) {
481 480 if (createsOutput()) {
482 481 *m->log->getWarn()
483 482 << m->message_prefix
... ... @@ -745,7 +744,8 @@ QPDFJob::doCheck(QPDF&amp; pdf)
745 744  
746 745 // Write the file to nowhere, uncompressing streams. This causes full file traversal and
747 746 // decoding of all streams we can decode.
748   - QPDFWriter w(pdf);
  747 + Writer::Config cfg;
  748 + Writer w(pdf, cfg);
749 749 Pl_Discard discard;
750 750 w.setOutputPipeline(&discard);
751 751 w.setDecodeLevel(qpdf_dl_all);
... ... @@ -804,7 +804,7 @@ QPDFJob::doShowObj(QPDF&amp; pdf)
804 804 m->log->saveToStandardOutput(true);
805 805 obj.pipeStreamData(
806 806 m->log->getSave().get(),
807   - (filter && m->normalize) ? qpdf_ef_normalize : 0,
  807 + filter && m->w_cfg.normalize_content() ? qpdf_ef_normalize : 0,
808 808 filter ? qpdf_dl_all : qpdf_dl_none);
809 809 }
810 810 } else {
... ... @@ -974,7 +974,7 @@ QPDFJob::doJSONObjects(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
974 974 p,
975 975 false,
976 976 first,
977   - m->decode_level,
  977 + m->w_cfg.decode_level(),
978 978 m->json_stream_data,
979 979 m->json_stream_prefix,
980 980 json_objects);
... ... @@ -1052,7 +1052,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1052 1052 j_image.addDictionaryMember("decodeparms", dp_array.getJSON(m->json_version));
1053 1053 j_image.addDictionaryMember(
1054 1054 "filterable",
1055   - JSON::makeBool(image.pipeStreamData(nullptr, 0, m->decode_level, true)));
  1055 + JSON::makeBool(image.pipeStreamData(nullptr, 0, m->w_cfg.decode_level(), true)));
1056 1056 }
1057 1057 j_page.addDictionaryMember("images", j_images);
1058 1058 JSON j_contents = j_page.addDictionaryMember("contents", JSON::makeArray());
... ... @@ -1569,7 +1569,7 @@ QPDFJob::doJSON(QPDF&amp; pdf, Pipeline* p)
1569 1569 JSON::writeDictionaryItem(p, first, "version", JSON::makeInt(m->json_version), 1);
1570 1570 JSON j_params = JSON::makeDictionary();
1571 1571 std::string decode_level_str;
1572   - switch (m->decode_level) {
  1572 + switch (m->w_cfg.decode_level()) {
1573 1573 case qpdf_dl_none:
1574 1574 decode_level_str = "none";
1575 1575 break;
... ... @@ -1709,7 +1709,7 @@ QPDFJob::doProcessOnce(
1709 1709 bool main_input)
1710 1710 {
1711 1711 pdf = std::make_unique<QPDF>();
1712   - pdf->doc().config(m->qcf.log(m->log));
  1712 + pdf->doc().config(m->d_cfg.log(m->log));
1713 1713 if (empty) {
1714 1714 pdf->emptyPDF();
1715 1715 } else if (main_input && m->json_input) {
... ... @@ -1739,7 +1739,7 @@ QPDFJob::doProcess(
1739 1739 // was incorrectly encoded, there's a good chance we'd succeed here.
1740 1740  
1741 1741 std::string ptemp;
1742   - if (password && !m->qcf.password_is_hex_key()) {
  1742 + if (password && !m->d_cfg.password_is_hex_key()) {
1743 1743 if (m->password_mode == QPDFJob::pm_hex_bytes) {
1744 1744 // Special case: handle --password-mode=hex-bytes for input password as well as output
1745 1745 // password
... ... @@ -1747,7 +1747,7 @@ QPDFJob::doProcess(
1747 1747 password = ptemp.c_str();
1748 1748 }
1749 1749 }
1750   - if (!password || empty || m->qcf.password_is_hex_key() || m->suppress_password_recovery) {
  1750 + if (!password || empty || m->d_cfg.password_is_hex_key() || m->suppress_password_recovery) {
1751 1751 // There is no password, or we're not doing recovery, so just do the normal processing with
1752 1752 // the supplied password.
1753 1753 doProcessOnce(pdf, fn, password, empty, used_for_input, main_input);
... ... @@ -2879,50 +2879,17 @@ parse_version(std::string const&amp; full_version_string, std::string&amp; version, int&amp;
2879 2879 }
2880 2880  
2881 2881 void
2882   -QPDFJob::setWriterOptions(QPDFWriter& w)
  2882 +QPDFJob::setWriterOptions(Writer& w)
2883 2883 {
2884 2884 if (m->compression_level >= 0) {
2885 2885 Pl_Flate::setCompressionLevel(m->compression_level);
2886 2886 }
2887   - if (m->qdf_mode) {
2888   - w.setQDFMode(true);
2889   - }
2890   - if (m->preserve_unreferenced_objects) {
2891   - w.setPreserveUnreferencedObjects(true);
2892   - }
2893   - if (m->newline_before_endstream) {
2894   - w.setNewlineBeforeEndstream(true);
2895   - }
2896   - if (m->normalize_set) {
2897   - w.setContentNormalization(m->normalize);
2898   - }
2899   - if (m->stream_data_set) {
2900   - w.setStreamDataMode(m->stream_data_mode);
2901   - }
2902   - if (m->compress_streams_set) {
2903   - w.setCompressStreams(m->compress_streams);
2904   - }
2905   - if (m->recompress_flate_set) {
2906   - w.setRecompressFlate(m->recompress_flate);
2907   - }
2908   - if (m->decode_level_set) {
2909   - w.setDecodeLevel(m->decode_level);
2910   - }
2911 2887 if (m->decrypt) {
2912 2888 w.setPreserveEncryption(false);
2913 2889 }
2914   - if (m->deterministic_id) {
2915   - w.setDeterministicID(true);
2916   - }
2917   - if (m->static_id) {
2918   - w.setStaticID(true);
2919   - }
2920 2890 if (m->static_aes_iv) {
2921 2891 w.setStaticAesIV(true);
2922 2892 }
2923   - if (m->suppress_original_object_id) {
2924   - w.setSuppressOriginalObjectIDs(true);
2925   - }
2926 2893 if (m->copy_encryption) {
2927 2894 std::unique_ptr<QPDF> encryption_pdf;
2928 2895 processFile(
... ... @@ -2936,15 +2903,6 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w)
2936 2903 if (m->encrypt) {
2937 2904 setEncryptionOptions(w);
2938 2905 }
2939   - if (m->linearize) {
2940   - w.setLinearization(true);
2941   - }
2942   - if (!m->linearize_pass1.empty()) {
2943   - w.setLinearizationPass1Filename(m->linearize_pass1);
2944   - }
2945   - if (m->object_stream_set) {
2946   - w.setObjectStreamMode(m->object_stream_mode);
2947   - }
2948 2906 w.setMinimumPDFVersion(m->max_input_version);
2949 2907 if (!m->min_version.empty()) {
2950 2908 std::string version;
... ... @@ -2961,15 +2919,13 @@ QPDFJob::setWriterOptions(QPDFWriter&amp; w)
2961 2919 if (m->progress) {
2962 2920 if (m->progress_handler) {
2963 2921 w.registerProgressReporter(
2964   - std::shared_ptr<QPDFWriter::ProgressReporter>(
2965   - new QPDFWriter::FunctionProgressReporter(m->progress_handler)));
  2922 + std::make_shared<QPDFWriter::FunctionProgressReporter>(m->progress_handler));
2966 2923 } else {
2967 2924 char const* outfilename =
2968 2925 !m->outfilename.empty() ? m->outfilename.data() : "standard output";
2969 2926 w.registerProgressReporter(
2970   - std::shared_ptr<QPDFWriter::ProgressReporter>(
2971   - // line-break
2972   - new ProgressReporter(*m->log->getInfo(), m->message_prefix, outfilename)));
  2927 + std::make_shared<ProgressReporter>(
  2928 + *m->log->getInfo(), m->message_prefix, outfilename));
2973 2929 }
2974 2930 }
2975 2931 }
... ... @@ -3014,7 +2970,7 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3014 2970 last = num_pages;
3015 2971 }
3016 2972 QPDF outpdf;
3017   - outpdf.doc().config(m->qcf);
  2973 + outpdf.doc().config(m->d_cfg);
3018 2974 outpdf.emptyPDF();
3019 2975 QPDFAcroFormDocumentHelper* out_afdh =
3020 2976 afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr;
... ... @@ -3052,7 +3008,8 @@ QPDFJob::doSplitPages(QPDF&amp; pdf)
3052 3008 if (QUtil::same_file(m->infile_nm(), outfile.data())) {
3053 3009 throw std::runtime_error("split pages would overwrite input file with " + outfile);
3054 3010 }
3055   - QPDFWriter w(outpdf, outfile.c_str());
  3011 + Writer w(outpdf, m->w_cfg);
  3012 + w.setOutputFilename(outfile.data());
3056 3013 setWriterOptions(w);
3057 3014 w.write();
3058 3015 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
... ... @@ -3077,9 +3034,8 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3077 3034 if (m->json_version) {
3078 3035 writeJSON(pdf);
3079 3036 } else {
3080   - // QPDFWriter must have block scope so the output file will be closed after write()
3081   - // finishes.
3082   - QPDFWriter w(pdf);
  3037 + // Writer must have block scope so the output file will be closed after write() finishes.
  3038 + Writer w(pdf, m->w_cfg);
3083 3039 if (!m->outfilename.empty()) {
3084 3040 w.setOutputFilename(m->outfilename.data());
3085 3041 } else {
... ...
libqpdf/QPDFJob_config.cc
... ... @@ -68,7 +68,7 @@ QPDFJob::Config*
68 68 QPDFJob::Config::check()
69 69 {
70 70 o.m->check = true;
71   - o.m->qcf.check_mode(true);
  71 + o.m->d_cfg.check_mode(true);
72 72 o.m->require_outfile = false;
73 73 return this;
74 74 }
... ... @@ -124,8 +124,7 @@ QPDFJob::Config::collate(std::string const&amp; parameter)
124 124 QPDFJob::Config*
125 125 QPDFJob::Config::compressStreams(std::string const& parameter)
126 126 {
127   - o.m->compress_streams_set = true;
128   - o.m->compress_streams = (parameter == "y");
  127 + o.m->w_cfg.compress_streams(parameter == "y");
129 128 return this;
130 129 }
131 130  
... ... @@ -146,7 +145,7 @@ QPDFJob::Config::jpegQuality(std::string const&amp; parameter)
146 145 QPDFJob::Config*
147 146 QPDFJob::Config::copyEncryption(std::string const& parameter)
148 147 {
149   - if (o.m->deterministic_id) {
  148 + if (o.m->w_cfg.deterministic_id()) {
150 149 usage("the deterministic-id option is incompatible with encrypted output files");
151 150 }
152 151 o.m->inputs.encryption_file = parameter;
... ... @@ -171,7 +170,7 @@ QPDFJob::Config::deterministicId()
171 170 if (o.m->encrypt || o.m->copy_encryption) {
172 171 usage("the deterministic-id option is incompatible with encrypted output files");
173 172 }
174   - o.m->deterministic_id = true;
  173 + o.m->w_cfg.deterministic_id(true);
175 174 return this;
176 175 }
177 176  
... ... @@ -234,7 +233,7 @@ QPDFJob::Config::generateAppearances()
234 233 QPDFJob::Config*
235 234 QPDFJob::Config::ignoreXrefStreams()
236 235 {
237   - o.m->qcf.ignore_xref_streams(true);
  236 + o.m->d_cfg.ignore_xref_streams(true);
238 237 return this;
239 238 }
240 239  
... ... @@ -327,9 +326,7 @@ QPDFJob::Config::jsonOutput(std::string const&amp; parameter)
327 326 // No need to set json_stream_data_set -- that indicates explicit use of --json-stream-data.
328 327 o.m->json_stream_data = qpdf_sj_inline;
329 328 }
330   - if (!o.m->decode_level_set) {
331   - o.m->decode_level = qpdf_dl_none;
332   - }
  329 + o.m->w_cfg.default_decode_level(qpdf_dl_none);
333 330 o.m->json_keys.insert("qpdf");
334 331 return this;
335 332 }
... ... @@ -373,14 +370,14 @@ QPDFJob::Config::keepInlineImages()
373 370 QPDFJob::Config*
374 371 QPDFJob::Config::linearize()
375 372 {
376   - o.m->linearize = true;
  373 + o.m->w_cfg.linearize(true);
377 374 return this;
378 375 }
379 376  
380 377 QPDFJob::Config*
381 378 QPDFJob::Config::linearizePass1(std::string const& parameter)
382 379 {
383   - o.m->linearize_pass1 = parameter;
  380 + o.m->w_cfg.linearize_pass1(parameter);
384 381 return this;
385 382 }
386 383  
... ... @@ -402,29 +399,28 @@ QPDFJob::Config::minVersion(std::string const&amp; parameter)
402 399 QPDFJob::Config*
403 400 QPDFJob::Config::newlineBeforeEndstream()
404 401 {
405   - o.m->newline_before_endstream = true;
  402 + o.m->w_cfg.newline_before_endstream(true);
406 403 return this;
407 404 }
408 405  
409 406 QPDFJob::Config*
410 407 QPDFJob::Config::noOriginalObjectIds()
411 408 {
412   - o.m->suppress_original_object_id = true;
  409 + o.m->w_cfg.no_original_object_ids(true);
413 410 return this;
414 411 }
415 412  
416 413 QPDFJob::Config*
417 414 QPDFJob::Config::noWarn()
418 415 {
419   - o.m->qcf.suppress_warnings(true);
  416 + o.m->d_cfg.suppress_warnings(true);
420 417 return this;
421 418 }
422 419  
423 420 QPDFJob::Config*
424 421 QPDFJob::Config::normalizeContent(std::string const& parameter)
425 422 {
426   - o.m->normalize_set = true;
427   - o.m->normalize = (parameter == "y");
  423 + o.m->w_cfg.normalize_content(parameter == "y");
428 424 return this;
429 425 }
430 426  
... ... @@ -466,14 +462,14 @@ QPDFJob::Config::password(std::string const&amp; parameter)
466 462 QPDFJob::Config*
467 463 QPDFJob::Config::passwordIsHexKey()
468 464 {
469   - o.m->qcf.password_is_hex_key(true);
  465 + o.m->d_cfg.password_is_hex_key(true);
470 466 return this;
471 467 }
472 468  
473 469 QPDFJob::Config*
474 470 QPDFJob::Config::preserveUnreferenced()
475 471 {
476   - o.m->preserve_unreferenced_objects = true;
  472 + o.m->w_cfg.preserve_unreferenced(true);
477 473 return this;
478 474 }
479 475  
... ... @@ -494,7 +490,7 @@ QPDFJob::Config::progress()
494 490 QPDFJob::Config*
495 491 QPDFJob::Config::qdf()
496 492 {
497   - o.m->qdf_mode = true;
  493 + o.m->w_cfg.qdf(true);
498 494 return this;
499 495 }
500 496  
... ... @@ -508,8 +504,7 @@ QPDFJob::Config::rawStreamData()
508 504 QPDFJob::Config*
509 505 QPDFJob::Config::recompressFlate()
510 506 {
511   - o.m->recompress_flate_set = true;
512   - o.m->recompress_flate = true;
  507 + o.m->w_cfg.recompress_flate(true);
513 508 return this;
514 509 }
515 510  
... ... @@ -649,7 +644,7 @@ QPDFJob::Config::staticAesIv()
649 644 QPDFJob::Config*
650 645 QPDFJob::Config::staticId()
651 646 {
652   - o.m->static_id = true;
  647 + o.m->w_cfg.static_id(true);
653 648 return this;
654 649 }
655 650  
... ... @@ -663,7 +658,7 @@ QPDFJob::Config::suppressPasswordRecovery()
663 658 QPDFJob::Config*
664 659 QPDFJob::Config::suppressRecovery()
665 660 {
666   - o.m->qcf.surpress_recovery(true);
  661 + o.m->d_cfg.surpress_recovery(true);
667 662 return this;
668 663 }
669 664  
... ... @@ -731,13 +726,12 @@ QPDFJob::Config::passwordMode(std::string const&amp; parameter)
731 726 QPDFJob::Config*
732 727 QPDFJob::Config::streamData(std::string const& parameter)
733 728 {
734   - o.m->stream_data_set = true;
735 729 if (parameter == "compress") {
736   - o.m->stream_data_mode = qpdf_s_compress;
  730 + o.m->w_cfg.stream_data(qpdf_s_compress);
737 731 } else if (parameter == "preserve") {
738   - o.m->stream_data_mode = qpdf_s_preserve;
  732 + o.m->w_cfg.stream_data(qpdf_s_preserve);
739 733 } else if (parameter == "uncompress") {
740   - o.m->stream_data_mode = qpdf_s_uncompress;
  734 + o.m->w_cfg.stream_data(qpdf_s_uncompress);
741 735 } else {
742 736 usage("invalid stream-data option");
743 737 }
... ... @@ -747,15 +741,14 @@ QPDFJob::Config::streamData(std::string const&amp; parameter)
747 741 QPDFJob::Config*
748 742 QPDFJob::Config::decodeLevel(std::string const& parameter)
749 743 {
750   - o.m->decode_level_set = true;
751 744 if (parameter == "none") {
752   - o.m->decode_level = qpdf_dl_none;
  745 + o.m->w_cfg.decode_level(qpdf_dl_none);
753 746 } else if (parameter == "generalized") {
754   - o.m->decode_level = qpdf_dl_generalized;
  747 + o.m->w_cfg.decode_level(qpdf_dl_generalized);
755 748 } else if (parameter == "specialized") {
756   - o.m->decode_level = qpdf_dl_specialized;
  749 + o.m->w_cfg.decode_level(qpdf_dl_specialized);
757 750 } else if (parameter == "all") {
758   - o.m->decode_level = qpdf_dl_all;
  751 + o.m->w_cfg.decode_level(qpdf_dl_all);
759 752 } else {
760 753 usage("invalid option");
761 754 }
... ... @@ -765,13 +758,12 @@ QPDFJob::Config::decodeLevel(std::string const&amp; parameter)
765 758 QPDFJob::Config*
766 759 QPDFJob::Config::objectStreams(std::string const& parameter)
767 760 {
768   - o.m->object_stream_set = true;
769 761 if (parameter == "disable") {
770   - o.m->object_stream_mode = qpdf_o_disable;
  762 + o.m->w_cfg.object_streams(qpdf_o_disable);
771 763 } else if (parameter == "preserve") {
772   - o.m->object_stream_mode = qpdf_o_preserve;
  764 + o.m->w_cfg.object_streams(qpdf_o_preserve);
773 765 } else if (parameter == "generate") {
774   - o.m->object_stream_mode = qpdf_o_generate;
  766 + o.m->w_cfg.object_streams(qpdf_o_generate);
775 767 } else {
776 768 usage("invalid object stream mode");
777 769 }
... ... @@ -1111,7 +1103,7 @@ std::shared_ptr&lt;QPDFJob::EncConfig&gt;
1111 1103 QPDFJob::Config::encrypt(
1112 1104 int keylen, std::string const& user_password, std::string const& owner_password)
1113 1105 {
1114   - if (o.m->deterministic_id) {
  1106 + if (o.m->w_cfg.deterministic_id()) {
1115 1107 usage("the deterministic-id option is incompatible with encrypted output files");
1116 1108 }
1117 1109 o.m->keylen = keylen;
... ...
libqpdf/QPDFWriter.cc
... ... @@ -27,8 +27,8 @@
27 27 using namespace std::literals;
28 28 using namespace qpdf;
29 29  
30   -using QDoc = QPDF::Doc;
31   -using Encryption = QDoc::Encryption;
  30 +using Encryption = impl::Doc::Encryption;
  31 +using Config = Writer::Config;
32 32  
33 33 QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default)
34 34 {
... ... @@ -262,300 +262,231 @@ Pl_stack::Popper::pop()
262 262 stack = nullptr;
263 263 }
264 264  
265   -// Writer class is restricted to QPDFWriter so that only it can call certain methods.
266   -class QPDF::Doc::Writer: QPDF::Doc::Common
  265 +namespace qpdf::impl
267 266 {
268   - friend class QPDFWriter;
269   - Writer(QPDF& qpdf) :
270   - Common(qpdf, qpdf.doc().m),
271   - lin(m->lin),
272   - objects(m->objects)
273   - {
274   - }
275   -
276   - protected:
277   - void
278   - optimize(
279   - QPDFWriter::ObjTable const& obj,
280   - std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
281   - {
282   - lin.optimize(obj, skip_stream_parameters);
283   - }
284   -
285   - void
286   - getLinearizedParts(
287   - QPDFWriter::ObjTable const& obj,
288   - std::vector<QPDFObjectHandle>& part4,
289   - std::vector<QPDFObjectHandle>& part6,
290   - std::vector<QPDFObjectHandle>& part7,
291   - std::vector<QPDFObjectHandle>& part8,
292   - std::vector<QPDFObjectHandle>& part9)
293   - {
294   - lin.getLinearizedParts(obj, part4, part6, part7, part8, part9);
295   - }
296   -
297   - void
298   - generateHintStream(
299   - QPDFWriter::NewObjTable const& new_obj,
300   - QPDFWriter::ObjTable const& obj,
301   - std::string& hint_stream,
302   - int& S,
303   - int& O,
304   - bool compressed)
305   - {
306   - lin.generateHintStream(new_obj, obj, hint_stream, S, O, compressed);
307   - }
308   -
309   - std::vector<QPDFObjGen>
310   - getCompressibleObjGens()
311   - {
312   - return objects.getCompressibleObjVector();
313   - }
314   -
315   - std::vector<bool>
316   - getCompressibleObjSet()
317   - {
318   - return objects.getCompressibleObjSet();
319   - }
320   -
321   - std::map<QPDFObjGen, QPDFXRefEntry> const&
322   - getXRefTable()
323   - {
324   - return objects.getXRefTableInternal();
325   - }
326   -
327   - size_t
328   - tableSize()
  267 + // Writer class is restricted to QPDFWriter so that only it can call certain methods.
  268 + class Writer: protected Doc::Common
329 269 {
330   - return qpdf.m->objects.tableSize();
331   - }
332   -
333   - QPDF::Doc::Linearization& lin;
334   - QPDF::Doc::Objects& objects;
335   -};
  270 + public:
  271 + // flags used by unparseObject
  272 + static int const f_stream = 1 << 0;
  273 + static int const f_filtered = 1 << 1;
  274 + static int const f_in_ostream = 1 << 2;
  275 + static int const f_hex_string = 1 << 3;
  276 + static int const f_no_encryption = 1 << 4;
  277 +
  278 + enum trailer_e { t_normal, t_lin_first, t_lin_second };
  279 +
  280 + Writer() = delete;
  281 + Writer(Writer const&) = delete;
  282 + Writer(Writer&&) = delete;
  283 + Writer& operator=(Writer const&) = delete;
  284 + Writer& operator=(Writer&&) = delete;
  285 + ~Writer()
  286 + {
  287 + if (file && close_file) {
  288 + fclose(file);
  289 + }
  290 + delete output_buffer;
  291 + }
  292 + Writer(QPDF& qpdf, QPDFWriter& w) :
  293 + Common(qpdf.doc()),
  294 + lin(qpdf.doc().linearization()),
  295 + cfg(true),
  296 + root_og(qpdf.getRoot().indirect() ? qpdf.getRoot().id_gen() : QPDFObjGen(-1, 0)),
  297 + pipeline_stack(pipeline)
  298 + {
  299 + }
336 300  
337   -class QPDFWriter::Members: QPDF::Doc::Writer
  301 + void write();
  302 + std::map<QPDFObjGen, QPDFXRefEntry> getWrittenXRefTable();
  303 + void setMinimumPDFVersion(std::string const& version, int extension_level = 0);
  304 + void copyEncryptionParameters(QPDF&);
  305 + void doWriteSetup();
  306 + void prepareFileForWrite();
  307 +
  308 + void disableIncompatibleEncryption(int major, int minor, int extension_level);
  309 + void interpretR3EncryptionParameters(
  310 + bool allow_accessibility,
  311 + bool allow_extract,
  312 + bool allow_assemble,
  313 + bool allow_annotate_and_form,
  314 + bool allow_form_filling,
  315 + bool allow_modify_other,
  316 + qpdf_r3_print_e print,
  317 + qpdf_r3_modify_e modify);
  318 + void setEncryptionParameters(char const* user_password, char const* owner_password);
  319 + void setEncryptionMinimumVersion();
  320 + void parseVersion(std::string const& version, int& major, int& minor) const;
  321 + int compareVersions(int major1, int minor1, int major2, int minor2) const;
  322 + void generateID(bool encrypted);
  323 + std::string getOriginalID1();
  324 + void initializeTables(size_t extra = 0);
  325 + void preserveObjectStreams();
  326 + void generateObjectStreams();
  327 + void initializeSpecialStreams();
  328 + void enqueue(QPDFObjectHandle const& object);
  329 + void enqueueObjectsStandard();
  330 + void enqueueObjectsPCLm();
  331 + void enqueuePart(std::vector<QPDFObjectHandle>& part);
  332 + void assignCompressedObjectNumbers(QPDFObjGen og);
  333 + Dictionary trimmed_trailer();
  334 +
  335 + // Returns tuple<filter, compress_stream, is_root_metadata>
  336 + std::tuple<const bool, const bool, const bool>
  337 + will_filter_stream(QPDFObjectHandle stream, std::string* stream_data);
  338 +
  339 + // Test whether stream would be filtered if it were written.
  340 + bool will_filter_stream(QPDFObjectHandle stream);
  341 + unsigned int bytesNeeded(long long n);
  342 + void writeBinary(unsigned long long val, unsigned int bytes);
  343 + Writer& write(std::string_view str);
  344 + Writer& write(size_t count, char c);
  345 + Writer& write(std::integral auto val);
  346 + Writer& write_name(std::string const& str);
  347 + Writer& write_string(std::string const& str, bool force_binary = false);
  348 + Writer& write_encrypted(std::string_view str);
  349 +
  350 + template <typename... Args>
  351 + Writer& write_qdf(Args&&... args);
  352 + template <typename... Args>
  353 + Writer& write_no_qdf(Args&&... args);
  354 + void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
  355 + void writeObjectStream(QPDFObjectHandle object);
  356 + void writeObject(QPDFObjectHandle object, int object_stream_index = -1);
  357 + void writeTrailer(
  358 + trailer_e which,
  359 + int size,
  360 + bool xref_stream,
  361 + qpdf_offset_t prev,
  362 + int linearization_pass);
  363 + void unparseObject(
  364 + QPDFObjectHandle object,
  365 + size_t level,
  366 + int flags,
  367 + // for stream dictionaries
  368 + size_t stream_length = 0,
  369 + bool compress = false);
  370 + void unparseChild(QPDFObjectHandle const& child, size_t level, int flags);
  371 + int openObject(int objid = 0);
  372 + void closeObject(int objid);
  373 + void writeStandard();
  374 + void writeLinearized();
  375 + void writeEncryptionDictionary();
  376 + void writeHeader();
  377 + void writeHintStream(int hint_id);
  378 + qpdf_offset_t writeXRefTable(trailer_e which, int first, int last, int size);
  379 + qpdf_offset_t writeXRefTable(
  380 + trailer_e which,
  381 + int first,
  382 + int last,
  383 + int size,
  384 + // for linearization
  385 + qpdf_offset_t prev,
  386 + bool suppress_offsets,
  387 + int hint_id,
  388 + qpdf_offset_t hint_offset,
  389 + qpdf_offset_t hint_length,
  390 + int linearization_pass);
  391 + qpdf_offset_t writeXRefStream(
  392 + int objid,
  393 + int max_id,
  394 + qpdf_offset_t max_offset,
  395 + trailer_e which,
  396 + int first,
  397 + int last,
  398 + int size);
  399 + qpdf_offset_t writeXRefStream(
  400 + int objid,
  401 + int max_id,
  402 + qpdf_offset_t max_offset,
  403 + trailer_e which,
  404 + int first,
  405 + int last,
  406 + int size,
  407 + // for linearization
  408 + qpdf_offset_t prev,
  409 + int hint_id,
  410 + qpdf_offset_t hint_offset,
  411 + qpdf_offset_t hint_length,
  412 + bool skip_compression,
  413 + int linearization_pass);
  414 +
  415 + void setDataKey(int objid);
  416 + void indicateProgress(bool decrement, bool finished);
  417 + size_t calculateXrefStreamPadding(qpdf_offset_t xref_bytes);
  418 +
  419 + void adjustAESStreamLength(size_t& length);
  420 + void computeDeterministicIDData();
  421 +
  422 + protected:
  423 + Doc::Linearization& lin;
  424 +
  425 + qpdf::Writer::Config cfg;
  426 +
  427 + QPDFObjGen root_og{-1, 0};
  428 + char const* filename{"unspecified"};
  429 + FILE* file{nullptr};
  430 + bool close_file{false};
  431 + std::unique_ptr<Pl_Buffer> buffer_pipeline{nullptr};
  432 + Buffer* output_buffer{nullptr};
  433 +
  434 + std::unique_ptr<QPDF::Doc::Encryption> encryption;
  435 + std::string encryption_key;
  436 +
  437 + std::string id1; // for /ID key of
  438 + std::string id2; // trailer dictionary
  439 + std::string final_pdf_version;
  440 + int final_extension_level{0};
  441 + std::string min_pdf_version;
  442 + int min_extension_level{0};
  443 + int encryption_dict_objid{0};
  444 + std::string cur_data_key;
  445 + std::unique_ptr<Pipeline> file_pl;
  446 + qpdf::pl::Count* pipeline{nullptr};
  447 + std::vector<QPDFObjectHandle> object_queue;
  448 + size_t object_queue_front{0};
  449 + QPDFWriter::ObjTable obj;
  450 + QPDFWriter::NewObjTable new_obj;
  451 + int next_objid{1};
  452 + int cur_stream_length_id{0};
  453 + size_t cur_stream_length{0};
  454 + bool added_newline{false};
  455 + size_t max_ostream_index{0};
  456 + std::set<QPDFObjGen> normalized_streams;
  457 + std::map<QPDFObjGen, int> page_object_to_seq;
  458 + std::map<QPDFObjGen, int> contents_to_page_seq;
  459 + std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects;
  460 + Pl_stack pipeline_stack;
  461 + std::string deterministic_id_data;
  462 + bool did_write_setup{false};
  463 +
  464 + // For progress reporting
  465 + std::shared_ptr<QPDFWriter::ProgressReporter> progress_reporter;
  466 + int events_expected{0};
  467 + int events_seen{0};
  468 + int next_progress_report{0};
  469 + }; // class qpdf::impl::Writer
  470 +
  471 +} // namespace qpdf::impl
  472 +
  473 +class QPDFWriter::Members: impl::Writer
338 474 {
339 475 friend class QPDFWriter;
  476 + friend class qpdf::Writer;
340 477  
341 478 public:
342   - // flags used by unparseObject
343   - static int const f_stream = 1 << 0;
344   - static int const f_filtered = 1 << 1;
345   - static int const f_in_ostream = 1 << 2;
346   - static int const f_hex_string = 1 << 3;
347   - static int const f_no_encryption = 1 << 4;
348   -
349   - enum trailer_e { t_normal, t_lin_first, t_lin_second };
350   -
351   - Members(QPDFWriter& w, QPDF& pdf) :
352   - QPDF::Doc::Writer(pdf),
353   - w(w),
354   - root_og(
355   - qpdf.getRoot().getObjGen().isIndirect() ? qpdf.getRoot().getObjGen()
356   - : QPDFObjGen(-1, 0)),
357   - pipeline_stack(pipeline)
  479 + Members(QPDFWriter& w, QPDF& qpdf) :
  480 + impl::Writer(qpdf, w)
358 481 {
359 482 }
360   -
361   - Members(Members const&) = delete;
362   -
363   - ~Members()
364   - {
365   - if (file && close_file) {
366   - fclose(file);
367   - }
368   - delete output_buffer;
369   - }
370   -
371   - void write();
372   - std::map<QPDFObjGen, QPDFXRefEntry> getWrittenXRefTable();
373   - void setMinimumPDFVersion(std::string const& version, int extension_level);
374   - void copyEncryptionParameters(QPDF&);
375   - void doWriteSetup();
376   - void prepareFileForWrite();
377   -
378   - void disableIncompatibleEncryption(int major, int minor, int extension_level);
379   - void interpretR3EncryptionParameters(
380   - bool allow_accessibility,
381   - bool allow_extract,
382   - bool allow_assemble,
383   - bool allow_annotate_and_form,
384   - bool allow_form_filling,
385   - bool allow_modify_other,
386   - qpdf_r3_print_e print,
387   - qpdf_r3_modify_e modify);
388   - void setEncryptionParameters(char const* user_password, char const* owner_password);
389   - void setEncryptionMinimumVersion();
390   - void parseVersion(std::string const& version, int& major, int& minor) const;
391   - int compareVersions(int major1, int minor1, int major2, int minor2) const;
392   - void generateID(bool encrypted);
393   - std::string getOriginalID1();
394   - void initializeTables(size_t extra = 0);
395   - void preserveObjectStreams();
396   - void generateObjectStreams();
397   - void initializeSpecialStreams();
398   - void enqueue(QPDFObjectHandle const& object);
399   - void enqueueObjectsStandard();
400   - void enqueueObjectsPCLm();
401   - void enqueuePart(std::vector<QPDFObjectHandle>& part);
402   - void assignCompressedObjectNumbers(QPDFObjGen og);
403   - Dictionary trimmed_trailer();
404   -
405   - // Returns tuple<filter, compress_stream, is_root_metadata>
406   - std::tuple<const bool, const bool, const bool>
407   - will_filter_stream(QPDFObjectHandle stream, std::string* stream_data);
408   -
409   - // Test whether stream would be filtered if it were written.
410   - bool will_filter_stream(QPDFObjectHandle stream);
411   - unsigned int bytesNeeded(long long n);
412   - void writeBinary(unsigned long long val, unsigned int bytes);
413   - Members& write(std::string_view str);
414   - Members& write(size_t count, char c);
415   - Members& write(std::integral auto val);
416   - Members& write_name(std::string const& str);
417   - Members& write_string(std::string const& str, bool force_binary = false);
418   - Members& write_encrypted(std::string_view str);
419   -
420   - template <typename... Args>
421   - Members& write_qdf(Args&&... args);
422   - template <typename... Args>
423   - Members& write_no_qdf(Args&&... args);
424   - void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
425   - void writeObjectStream(QPDFObjectHandle object);
426   - void writeObject(QPDFObjectHandle object, int object_stream_index = -1);
427   - void writeTrailer(
428   - trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass);
429   - void unparseObject(
430   - QPDFObjectHandle object,
431   - size_t level,
432   - int flags,
433   - // for stream dictionaries
434   - size_t stream_length = 0,
435   - bool compress = false);
436   - void unparseChild(QPDFObjectHandle const& child, size_t level, int flags);
437   - int openObject(int objid = 0);
438   - void closeObject(int objid);
439   - void writeStandard();
440   - void writeLinearized();
441   - void writeEncryptionDictionary();
442   - void writeHeader();
443   - void writeHintStream(int hint_id);
444   - qpdf_offset_t writeXRefTable(trailer_e which, int first, int last, int size);
445   - qpdf_offset_t writeXRefTable(
446   - trailer_e which,
447   - int first,
448   - int last,
449   - int size,
450   - // for linearization
451   - qpdf_offset_t prev,
452   - bool suppress_offsets,
453   - int hint_id,
454   - qpdf_offset_t hint_offset,
455   - qpdf_offset_t hint_length,
456   - int linearization_pass);
457   - qpdf_offset_t writeXRefStream(
458   - int objid,
459   - int max_id,
460   - qpdf_offset_t max_offset,
461   - trailer_e which,
462   - int first,
463   - int last,
464   - int size);
465   - qpdf_offset_t writeXRefStream(
466   - int objid,
467   - int max_id,
468   - qpdf_offset_t max_offset,
469   - trailer_e which,
470   - int first,
471   - int last,
472   - int size,
473   - // for linearization
474   - qpdf_offset_t prev,
475   - int hint_id,
476   - qpdf_offset_t hint_offset,
477   - qpdf_offset_t hint_length,
478   - bool skip_compression,
479   - int linearization_pass);
480   -
481   - void setDataKey(int objid);
482   - void indicateProgress(bool decrement, bool finished);
483   - size_t calculateXrefStreamPadding(qpdf_offset_t xref_bytes);
484   -
485   - void adjustAESStreamLength(size_t& length);
486   - void computeDeterministicIDData();
487   -
488   - private:
489   - QPDFWriter& w;
490   - QPDFObjGen root_og{-1, 0};
491   - char const* filename{"unspecified"};
492   - FILE* file{nullptr};
493   - bool close_file{false};
494   - std::unique_ptr<Pl_Buffer> buffer_pipeline{nullptr};
495   - Buffer* output_buffer{nullptr};
496   - bool normalize_content_set{false};
497   - bool normalize_content{false};
498   - bool compress_streams{true};
499   - bool compress_streams_set{false};
500   - qpdf_stream_decode_level_e stream_decode_level{qpdf_dl_generalized};
501   - bool stream_decode_level_set{false};
502   - bool recompress_flate{false};
503   - bool qdf_mode{false};
504   - bool preserve_unreferenced_objects{false};
505   - bool newline_before_endstream{false};
506   - bool static_id{false};
507   - bool suppress_original_object_ids{false};
508   - bool direct_stream_lengths{true};
509   - bool preserve_encryption{true};
510   - bool linearized{false};
511   - bool pclm{false};
512   - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve};
513   -
514   - std::unique_ptr<QPDF::Doc::Encryption> encryption;
515   - std::string encryption_key;
516   - bool encrypt_use_aes{false};
517   -
518   - std::string id1; // for /ID key of
519   - std::string id2; // trailer dictionary
520   - std::string final_pdf_version;
521   - int final_extension_level{0};
522   - std::string min_pdf_version;
523   - int min_extension_level{0};
524   - std::string forced_pdf_version;
525   - int forced_extension_level{0};
526   - std::string extra_header_text;
527   - int encryption_dict_objid{0};
528   - std::string cur_data_key;
529   - std::unique_ptr<Pipeline> file_pl;
530   - qpdf::pl::Count* pipeline{nullptr};
531   - std::vector<QPDFObjectHandle> object_queue;
532   - size_t object_queue_front{0};
533   - QPDFWriter::ObjTable obj;
534   - QPDFWriter::NewObjTable new_obj;
535   - int next_objid{1};
536   - int cur_stream_length_id{0};
537   - size_t cur_stream_length{0};
538   - bool added_newline{false};
539   - size_t max_ostream_index{0};
540   - std::set<QPDFObjGen> normalized_streams;
541   - std::map<QPDFObjGen, int> page_object_to_seq;
542   - std::map<QPDFObjGen, int> contents_to_page_seq;
543   - std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects;
544   - Pl_stack pipeline_stack;
545   - bool deterministic_id{false};
546   - std::string deterministic_id_data;
547   - bool did_write_setup{false};
548   -
549   - // For linearization only
550   - std::string lin_pass1_filename;
551   -
552   - // For progress reporting
553   - std::shared_ptr<QPDFWriter::ProgressReporter> progress_reporter;
554   - int events_expected{0};
555   - int events_seen{0};
556   - int next_progress_report{0};
557 483 };
558 484  
  485 +qpdf::Writer::Writer(QPDF& qpdf, Config cfg) :
  486 + QPDFWriter(qpdf)
  487 +{
  488 + m->cfg = cfg;
  489 +}
559 490 QPDFWriter::QPDFWriter(QPDF& pdf) :
560 491 m(std::make_shared<Members>(*this, pdf))
561 492 {
... ... @@ -632,75 +563,129 @@ QPDFWriter::setOutputPipeline(Pipeline* p)
632 563 void
633 564 QPDFWriter::setObjectStreamMode(qpdf_object_stream_e mode)
634 565 {
635   - m->object_stream_mode = mode;
  566 + m->cfg.object_streams(mode);
636 567 }
637 568  
638 569 void
639 570 QPDFWriter::setStreamDataMode(qpdf_stream_data_e mode)
640 571 {
  572 + m->cfg.stream_data(mode);
  573 +}
  574 +
  575 +Config&
  576 +Config::stream_data(qpdf_stream_data_e mode)
  577 +{
641 578 switch (mode) {
642 579 case qpdf_s_uncompress:
643   - m->stream_decode_level = std::max(qpdf_dl_generalized, m->stream_decode_level);
644   - m->compress_streams = false;
645   - break;
  580 + decode_level(std::max(qpdf_dl_generalized, decode_level_));
  581 + compress_streams(false);
  582 + return *this;
646 583  
647 584 case qpdf_s_preserve:
648   - m->stream_decode_level = qpdf_dl_none;
649   - m->compress_streams = false;
650   - break;
  585 + decode_level(qpdf_dl_none);
  586 + compress_streams(false);
  587 + return *this;
651 588  
652 589 case qpdf_s_compress:
653   - m->stream_decode_level = std::max(qpdf_dl_generalized, m->stream_decode_level);
654   - m->compress_streams = true;
655   - break;
  590 + decode_level(std::max(qpdf_dl_generalized, decode_level_));
  591 + compress_streams(true);
656 592 }
657   - m->stream_decode_level_set = true;
658   - m->compress_streams_set = true;
  593 + return *this;
659 594 }
660 595  
661 596 void
662 597 QPDFWriter::setCompressStreams(bool val)
663 598 {
664   - m->compress_streams = val;
665   - m->compress_streams_set = true;
  599 + m->cfg.compress_streams(val);
  600 +}
  601 +
  602 +Config&
  603 +Config::compress_streams(bool val)
  604 +{
  605 + if (pclm_) {
  606 + usage("compress_streams cannot be set when pclm is set");
  607 + return *this;
  608 + }
  609 + compress_streams_set_ = true;
  610 + compress_streams_ = val;
  611 + return *this;
666 612 }
667 613  
668 614 void
669 615 QPDFWriter::setDecodeLevel(qpdf_stream_decode_level_e val)
670 616 {
671   - m->stream_decode_level = val;
672   - m->stream_decode_level_set = true;
  617 + m->cfg.decode_level(val);
  618 +}
  619 +
  620 +Config&
  621 +Config::decode_level(qpdf_stream_decode_level_e val)
  622 +{
  623 + if (pclm_) {
  624 + usage("stream_decode_level cannot be set when pclm is set");
  625 + return *this;
  626 + }
  627 + decode_level_set_ = true;
  628 + decode_level_ = val;
  629 + return *this;
673 630 }
674 631  
675 632 void
676 633 QPDFWriter::setRecompressFlate(bool val)
677 634 {
678   - m->recompress_flate = val;
  635 + m->cfg.recompress_flate(val);
679 636 }
680 637  
681 638 void
682 639 QPDFWriter::setContentNormalization(bool val)
683 640 {
684   - m->normalize_content_set = true;
685   - m->normalize_content = val;
  641 + m->cfg.normalize_content(val);
686 642 }
687 643  
688 644 void
689 645 QPDFWriter::setQDFMode(bool val)
690 646 {
691   - m->qdf_mode = val;
  647 + m->cfg.qdf(val);
  648 +}
  649 +
  650 +Config&
  651 +Config::qdf(bool val)
  652 +{
  653 + if (pclm_ || linearize_) {
  654 + usage("qdf cannot be set when linearize or pclm are set");
  655 + }
  656 + if (preserve_encryption_) {
  657 + usage("preserve_encryption cannot be set when qdf is set");
  658 + }
  659 + qdf_ = val;
  660 + if (val) {
  661 + if (!normalize_content_set_) {
  662 + normalize_content(true);
  663 + }
  664 + if (!compress_streams_set_) {
  665 + compress_streams(false);
  666 + }
  667 + if (!decode_level_set_) {
  668 + decode_level(qpdf_dl_generalized);
  669 + }
  670 + preserve_encryption_ = false;
  671 + // Generate indirect stream lengths for qdf mode since fix-qdf uses them for storing
  672 + // recomputed stream length data. Certain streams such as object streams, xref streams, and
  673 + // hint streams always get direct stream lengths.
  674 + direct_stream_lengths_ = false;
  675 + }
  676 + return *this;
692 677 }
693 678  
694 679 void
695 680 QPDFWriter::setPreserveUnreferencedObjects(bool val)
696 681 {
697   - m->preserve_unreferenced_objects = val;
  682 + m->cfg.preserve_unreferenced(val);
698 683 }
699 684  
700 685 void
701 686 QPDFWriter::setNewlineBeforeEndstream(bool val)
702 687 {
703   - m->newline_before_endstream = val;
  688 + m->cfg.newline_before_endstream(val);
704 689 }
705 690  
706 691 void
... ... @@ -710,7 +695,7 @@ QPDFWriter::setMinimumPDFVersion(std::string const&amp; version, int extension_level
710 695 }
711 696  
712 697 void
713   -QPDFWriter::Members::setMinimumPDFVersion(std::string const& version, int extension_level)
  698 +impl::Writer::setMinimumPDFVersion(std::string const& version, int extension_level)
714 699 {
715 700 bool set_version = false;
716 701 bool set_extension_level = false;
... ... @@ -756,31 +741,37 @@ QPDFWriter::setMinimumPDFVersion(PDFVersion const&amp; v)
756 741 void
757 742 QPDFWriter::forcePDFVersion(std::string const& version, int extension_level)
758 743 {
759   - m->forced_pdf_version = version;
760   - m->forced_extension_level = extension_level;
  744 + m->cfg.forced_pdf_version(version, extension_level);
761 745 }
762 746  
763 747 void
764 748 QPDFWriter::setExtraHeaderText(std::string const& text)
765 749 {
766   - m->extra_header_text = text;
767   - if (!m->extra_header_text.empty() && *m->extra_header_text.rbegin() != '\n') {
768   - m->extra_header_text += "\n";
  750 + m->cfg.extra_header_text(text);
  751 +}
  752 +
  753 +Config&
  754 +Config::extra_header_text(std::string const& val)
  755 +{
  756 + extra_header_text_ = val;
  757 + if (!extra_header_text_.empty() && extra_header_text_.back() != '\n') {
  758 + extra_header_text_ += "\n";
769 759 } else {
770 760 QTC::TC("qpdf", "QPDFWriter extra header text no newline");
771 761 }
  762 + return *this;
772 763 }
773 764  
774 765 void
775 766 QPDFWriter::setStaticID(bool val)
776 767 {
777   - m->static_id = val;
  768 + m->cfg.static_id(val);
778 769 }
779 770  
780 771 void
781 772 QPDFWriter::setDeterministicID(bool val)
782 773 {
783   - m->deterministic_id = val;
  774 + m->cfg.deterministic_id(val);
784 775 }
785 776  
786 777 void
... ... @@ -794,37 +785,61 @@ QPDFWriter::setStaticAesIV(bool val)
794 785 void
795 786 QPDFWriter::setSuppressOriginalObjectIDs(bool val)
796 787 {
797   - m->suppress_original_object_ids = val;
  788 + m->cfg.no_original_object_ids(val);
798 789 }
799 790  
800 791 void
801 792 QPDFWriter::setPreserveEncryption(bool val)
802 793 {
803   - m->preserve_encryption = val;
  794 + m->cfg.preserve_encryption(val);
804 795 }
805 796  
806 797 void
807 798 QPDFWriter::setLinearization(bool val)
808 799 {
809   - m->linearized = val;
810   - if (val) {
811   - m->pclm = false;
  800 + m->cfg.linearize(val);
  801 +}
  802 +
  803 +Config&
  804 +Config::linearize(bool val)
  805 +{
  806 + if (pclm_ || qdf_) {
  807 + usage("linearize cannot be set when qdf or pclm are set");
  808 + return *this;
812 809 }
  810 + linearize_ = val;
  811 + return *this;
813 812 }
814 813  
815 814 void
816 815 QPDFWriter::setLinearizationPass1Filename(std::string const& filename)
817 816 {
818   - m->lin_pass1_filename = filename;
  817 + m->cfg.linearize_pass1(filename);
819 818 }
820 819  
821 820 void
822 821 QPDFWriter::setPCLm(bool val)
823 822 {
824   - m->pclm = val;
  823 + m->cfg.pclm(val);
  824 +}
  825 +
  826 +Config&
  827 +Config::pclm(bool val)
  828 +{
  829 + if (decode_level_set_ || compress_streams_set_ || linearize_) {
  830 + usage(
  831 + "pclm cannot be set when stream_decode_level, compress_streams, linearize or qdf are "
  832 + "set");
  833 + return *this;
  834 + }
  835 + pclm_ = val;
825 836 if (val) {
826   - m->linearized = false;
  837 + decode_level_ = qpdf_dl_none;
  838 + compress_streams_ = false;
  839 + linearize_ = false;
827 840 }
  841 +
  842 + return *this;
828 843 }
829 844  
830 845 void
... ... @@ -892,7 +907,7 @@ QPDFWriter::setR4EncryptionParametersInsecure(
892 907 bool use_aes)
893 908 {
894 909 m->encryption = std::make_unique<Encryption>(4, 4, 16, encrypt_metadata);
895   - m->encrypt_use_aes = use_aes;
  910 + m->cfg.encrypt_use_aes(use_aes);
896 911 m->interpretR3EncryptionParameters(
897 912 allow_accessibility,
898 913 allow_extract,
... ... @@ -919,7 +934,7 @@ QPDFWriter::setR5EncryptionParameters(
919 934 bool encrypt_metadata)
920 935 {
921 936 m->encryption = std::make_unique<Encryption>(5, 5, 32, encrypt_metadata);
922   - m->encrypt_use_aes = true;
  937 + m->cfg.encrypt_use_aes(true);
923 938 m->interpretR3EncryptionParameters(
924 939 allow_accessibility,
925 940 allow_extract,
... ... @@ -955,12 +970,12 @@ QPDFWriter::setR6EncryptionParameters(
955 970 allow_modify_other,
956 971 print,
957 972 qpdf_r3m_all);
958   - m->encrypt_use_aes = true;
  973 + m->cfg.encrypt_use_aes(true);
959 974 m->setEncryptionParameters(user_password, owner_password);
960 975 }
961 976  
962 977 void
963   -QPDFWriter::Members::interpretR3EncryptionParameters(
  978 +impl::Writer::interpretR3EncryptionParameters(
964 979 bool allow_accessibility,
965 980 bool allow_extract,
966 981 bool allow_assemble,
... ... @@ -1059,7 +1074,7 @@ QPDFWriter::Members::interpretR3EncryptionParameters(
1059 1074 }
1060 1075  
1061 1076 void
1062   -QPDFWriter::Members::setEncryptionParameters(char const* user_password, char const* owner_password)
  1077 +impl::Writer::setEncryptionParameters(char const* user_password, char const* owner_password)
1063 1078 {
1064 1079 generateID(true);
1065 1080 encryption->setId1(id1);
... ... @@ -1074,9 +1089,9 @@ QPDFWriter::copyEncryptionParameters(QPDF&amp; qpdf)
1074 1089 }
1075 1090  
1076 1091 void
1077   -QPDFWriter::Members::copyEncryptionParameters(QPDF& qpdf)
  1092 +impl::Writer::copyEncryptionParameters(QPDF& qpdf)
1078 1093 {
1079   - preserve_encryption = false;
  1094 + cfg.preserve_encryption(false);
1080 1095 QPDFObjectHandle trailer = qpdf.getTrailer();
1081 1096 if (trailer.hasKey("/Encrypt")) {
1082 1097 generateID(true);
... ... @@ -1096,10 +1111,10 @@ QPDFWriter::Members::copyEncryptionParameters(QPDF&amp; qpdf)
1096 1111 // Acrobat doesn't create files with V >= 4 that don't use AES, and the logic of
1097 1112 // figuring out whether AES is used or not is complicated with /StmF, /StrF, and /EFF
1098 1113 // all potentially having different values.
1099   - encrypt_use_aes = true;
  1114 + cfg.encrypt_use_aes(true);
1100 1115 }
1101 1116 QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1);
1102   - QTC::TC("qpdf", "QPDFWriter copy use_aes", encrypt_use_aes ? 0 : 1);
  1117 + QTC::TC("qpdf", "QPDFWriter copy use_aes", cfg.encrypt_use_aes() ? 0 : 1);
1103 1118  
1104 1119 encryption = std::make_unique<Encryption>(
1105 1120 V,
... ... @@ -1120,7 +1135,7 @@ QPDFWriter::Members::copyEncryptionParameters(QPDF&amp; qpdf)
1120 1135 }
1121 1136  
1122 1137 void
1123   -QPDFWriter::Members::disableIncompatibleEncryption(int major, int minor, int extension_level)
  1138 +impl::Writer::disableIncompatibleEncryption(int major, int minor, int extension_level)
1124 1139 {
1125 1140 if (!encryption) {
1126 1141 return;
... ... @@ -1140,7 +1155,7 @@ QPDFWriter::Members::disableIncompatibleEncryption(int major, int minor, int ext
1140 1155 encryption = nullptr;
1141 1156 }
1142 1157 } else if (compareVersions(major, minor, 1, 6) < 0) {
1143   - if (encrypt_use_aes) {
  1158 + if (cfg.encrypt_use_aes()) {
1144 1159 encryption = nullptr;
1145 1160 }
1146 1161 } else if (
... ... @@ -1157,7 +1172,7 @@ QPDFWriter::Members::disableIncompatibleEncryption(int major, int minor, int ext
1157 1172 }
1158 1173  
1159 1174 void
1160   -QPDFWriter::Members::parseVersion(std::string const& version, int& major, int& minor) const
  1175 +impl::Writer::parseVersion(std::string const& version, int& major, int& minor) const
1161 1176 {
1162 1177 major = QUtil::string_to_int(version.c_str());
1163 1178 minor = 0;
... ... @@ -1174,7 +1189,7 @@ QPDFWriter::Members::parseVersion(std::string const&amp; version, int&amp; major, int&amp; m
1174 1189 }
1175 1190  
1176 1191 int
1177   -QPDFWriter::Members::compareVersions(int major1, int minor1, int major2, int minor2) const
  1192 +impl::Writer::compareVersions(int major1, int minor1, int major2, int minor2) const
1178 1193 {
1179 1194 if (major1 < major2) {
1180 1195 return -1;
... ... @@ -1189,33 +1204,38 @@ QPDFWriter::Members::compareVersions(int major1, int minor1, int major2, int min
1189 1204 }
1190 1205  
1191 1206 void
1192   -QPDFWriter::Members::setEncryptionMinimumVersion()
  1207 +impl::Writer::setEncryptionMinimumVersion()
1193 1208 {
1194 1209 auto const R = encryption->getR();
1195 1210 if (R >= 6) {
1196   - w.setMinimumPDFVersion("1.7", 8);
  1211 + setMinimumPDFVersion("1.7", 8);
1197 1212 } else if (R == 5) {
1198   - w.setMinimumPDFVersion("1.7", 3);
  1213 + setMinimumPDFVersion("1.7", 3);
1199 1214 } else if (R == 4) {
1200   - w.setMinimumPDFVersion(encrypt_use_aes ? "1.6" : "1.5");
  1215 + setMinimumPDFVersion(cfg.encrypt_use_aes() ? "1.6" : "1.5");
1201 1216 } else if (R == 3) {
1202   - w.setMinimumPDFVersion("1.4");
  1217 + setMinimumPDFVersion("1.4");
1203 1218 } else {
1204   - w.setMinimumPDFVersion("1.3");
  1219 + setMinimumPDFVersion("1.3");
1205 1220 }
1206 1221 }
1207 1222  
1208 1223 void
1209   -QPDFWriter::Members::setDataKey(int objid)
  1224 +impl::Writer::setDataKey(int objid)
1210 1225 {
1211 1226 if (encryption) {
1212 1227 cur_data_key = QPDF::compute_data_key(
1213   - encryption_key, objid, 0, encrypt_use_aes, encryption->getV(), encryption->getR());
  1228 + encryption_key,
  1229 + objid,
  1230 + 0,
  1231 + cfg.encrypt_use_aes(),
  1232 + encryption->getV(),
  1233 + encryption->getR());
1214 1234 }
1215 1235 }
1216 1236  
1217 1237 unsigned int
1218   -QPDFWriter::Members::bytesNeeded(long long n)
  1238 +impl::Writer::bytesNeeded(long long n)
1219 1239 {
1220 1240 unsigned int bytes = 0;
1221 1241 while (n) {
... ... @@ -1226,7 +1246,7 @@ QPDFWriter::Members::bytesNeeded(long long n)
1226 1246 }
1227 1247  
1228 1248 void
1229   -QPDFWriter::Members::writeBinary(unsigned long long val, unsigned int bytes)
  1249 +impl::Writer::writeBinary(unsigned long long val, unsigned int bytes)
1230 1250 {
1231 1251 if (bytes > sizeof(unsigned long long)) {
1232 1252 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)
1239 1259 pipeline->write(data, bytes);
1240 1260 }
1241 1261  
1242   -QPDFWriter::Members&
1243   -QPDFWriter::Members::write(std::string_view str)
  1262 +impl::Writer&
  1263 +impl::Writer::write(std::string_view str)
1244 1264 {
1245 1265 pipeline->write(str);
1246 1266 return *this;
1247 1267 }
1248 1268  
1249   -QPDFWriter::Members&
1250   -QPDFWriter::Members::write(std::integral auto val)
  1269 +impl::Writer&
  1270 +impl::Writer::write(std::integral auto val)
1251 1271 {
1252 1272 pipeline->write(std::to_string(val));
1253 1273 return *this;
1254 1274 }
1255 1275  
1256   -QPDFWriter::Members&
1257   -QPDFWriter::Members::write(size_t count, char c)
  1276 +impl::Writer&
  1277 +impl::Writer::write(size_t count, char c)
1258 1278 {
1259 1279 pipeline->write(count, c);
1260 1280 return *this;
1261 1281 }
1262 1282  
1263   -QPDFWriter::Members&
1264   -QPDFWriter::Members::write_name(std::string const& str)
  1283 +impl::Writer&
  1284 +impl::Writer::write_name(std::string const& str)
1265 1285 {
1266 1286 pipeline->write(Name::normalize(str));
1267 1287 return *this;
1268 1288 }
1269 1289  
1270   -QPDFWriter::Members&
1271   -QPDFWriter::Members::write_string(std::string const& str, bool force_binary)
  1290 +impl::Writer&
  1291 +impl::Writer::write_string(std::string const& str, bool force_binary)
1272 1292 {
1273 1293 pipeline->write(QPDF_String(str).unparse(force_binary));
1274 1294 return *this;
1275 1295 }
1276 1296  
1277 1297 template <typename... Args>
1278   -QPDFWriter::Members&
1279   -QPDFWriter::Members::write_qdf(Args&&... args)
  1298 +impl::Writer&
  1299 +impl::Writer::write_qdf(Args&&... args)
1280 1300 {
1281   - if (qdf_mode) {
  1301 + if (cfg.qdf()) {
1282 1302 pipeline->write(std::forward<Args>(args)...);
1283 1303 }
1284 1304 return *this;
1285 1305 }
1286 1306  
1287 1307 template <typename... Args>
1288   -QPDFWriter::Members&
1289   -QPDFWriter::Members::write_no_qdf(Args&&... args)
  1308 +impl::Writer&
  1309 +impl::Writer::write_no_qdf(Args&&... args)
1290 1310 {
1291   - if (!qdf_mode) {
  1311 + if (!cfg.qdf()) {
1292 1312 pipeline->write(std::forward<Args>(args)...);
1293 1313 }
1294 1314 return *this;
1295 1315 }
1296 1316  
1297 1317 void
1298   -QPDFWriter::Members::adjustAESStreamLength(size_t& length)
  1318 +impl::Writer::adjustAESStreamLength(size_t& length)
1299 1319 {
1300   - if (encryption && !cur_data_key.empty() && encrypt_use_aes) {
  1320 + if (encryption && !cur_data_key.empty() && cfg.encrypt_use_aes()) {
1301 1321 // Stream length will be padded with 1 to 16 bytes to end up as a multiple of 16. It will
1302 1322 // also be prepended by 16 bits of random data.
1303 1323 length += 32 - (length & 0xf);
1304 1324 }
1305 1325 }
1306 1326  
1307   -QPDFWriter::Members&
1308   -QPDFWriter::Members::write_encrypted(std::string_view str)
  1327 +impl::Writer&
  1328 +impl::Writer::write_encrypted(std::string_view str)
1309 1329 {
1310 1330 if (!(encryption && !cur_data_key.empty())) {
1311 1331 write(str);
1312   - } else if (encrypt_use_aes) {
  1332 + } else if (cfg.encrypt_use_aes()) {
1313 1333 write(pl::pipe<Pl_AES_PDF>(str, true, cur_data_key));
1314 1334 } else {
1315 1335 write(pl::pipe<Pl_RC4>(str, cur_data_key));
... ... @@ -1319,7 +1339,7 @@ QPDFWriter::Members::write_encrypted(std::string_view str)
1319 1339 }
1320 1340  
1321 1341 void
1322   -QPDFWriter::Members::computeDeterministicIDData()
  1342 +impl::Writer::computeDeterministicIDData()
1323 1343 {
1324 1344 if (!id2.empty()) {
1325 1345 // Can't happen in the code
... ... @@ -1331,7 +1351,7 @@ QPDFWriter::Members::computeDeterministicIDData()
1331 1351 }
1332 1352  
1333 1353 int
1334   -QPDFWriter::Members::openObject(int objid)
  1354 +impl::Writer::openObject(int objid)
1335 1355 {
1336 1356 if (objid == 0) {
1337 1357 objid = next_objid++;
... ... @@ -1342,7 +1362,7 @@ QPDFWriter::Members::openObject(int objid)
1342 1362 }
1343 1363  
1344 1364 void
1345   -QPDFWriter::Members::closeObject(int objid)
  1365 +impl::Writer::closeObject(int objid)
1346 1366 {
1347 1367 // Write a newline before endobj as it makes the file easier to repair.
1348 1368 write("\nendobj\n").write_qdf("\n");
... ... @@ -1351,7 +1371,7 @@ QPDFWriter::Members::closeObject(int objid)
1351 1371 }
1352 1372  
1353 1373 void
1354   -QPDFWriter::Members::assignCompressedObjectNumbers(QPDFObjGen og)
  1374 +impl::Writer::assignCompressedObjectNumbers(QPDFObjGen og)
1355 1375 {
1356 1376 int objid = og.getObj();
1357 1377 if (og.getGen() != 0 || !object_stream_to_objects.contains(objid)) {
... ... @@ -1366,7 +1386,7 @@ QPDFWriter::Members::assignCompressedObjectNumbers(QPDFObjGen og)
1366 1386 }
1367 1387  
1368 1388 void
1369   -QPDFWriter::Members::enqueue(QPDFObjectHandle const& object)
  1389 +impl::Writer::enqueue(QPDFObjectHandle const& object)
1370 1390 {
1371 1391 if (object.indirect()) {
1372 1392 util::assertion(
... ... @@ -1380,7 +1400,7 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const&amp; object)
1380 1400 "Use QPDF::copyForeignObject to add objects from another file." //
1381 1401 );
1382 1402  
1383   - if (qdf_mode && object.isStreamOfType("/XRef")) {
  1403 + if (cfg.qdf() && object.isStreamOfType("/XRef")) {
1384 1404 // As a special case, do not output any extraneous XRef streams in QDF mode. Doing so
1385 1405 // will confuse fix-qdf, which expects to see only one XRef stream at the end of the
1386 1406 // 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&amp; object)
1406 1426 if (og.getGen() == 0 && object_stream_to_objects.contains(og.getObj())) {
1407 1427 // For linearized files, uncompressed objects go at end, and we take care of
1408 1428 // assigning numbers to them elsewhere.
1409   - if (!linearized) {
  1429 + if (!cfg.linearize()) {
1410 1430 assignCompressedObjectNumbers(og);
1411 1431 }
1412   - } else if (!direct_stream_lengths && object.isStream()) {
  1432 + } else if (!cfg.direct_stream_lengths() && object.isStream()) {
1413 1433 // reserve next object ID for length
1414 1434 ++next_objid;
1415 1435 }
... ... @@ -1418,7 +1438,7 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const&amp; object)
1418 1438 return;
1419 1439 }
1420 1440  
1421   - if (linearized) {
  1441 + if (cfg.linearize()) {
1422 1442 return;
1423 1443 }
1424 1444  
... ... @@ -1437,9 +1457,9 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const&amp; object)
1437 1457 }
1438 1458  
1439 1459 void
1440   -QPDFWriter::Members::unparseChild(QPDFObjectHandle const& child, size_t level, int flags)
  1460 +impl::Writer::unparseChild(QPDFObjectHandle const& child, size_t level, int flags)
1441 1461 {
1442   - if (!linearized) {
  1462 + if (!cfg.linearize()) {
1443 1463 enqueue(child);
1444 1464 }
1445 1465 if (child.indirect()) {
... ... @@ -1450,7 +1470,7 @@ QPDFWriter::Members::unparseChild(QPDFObjectHandle const&amp; child, size_t level, i
1450 1470 }
1451 1471  
1452 1472 void
1453   -QPDFWriter::Members::writeTrailer(
  1473 +impl::Writer::writeTrailer(
1454 1474 trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass)
1455 1475 {
1456 1476 auto trailer = trimmed_trailer();
... ... @@ -1498,7 +1518,7 @@ QPDFWriter::Members::writeTrailer(
1498 1518 }
1499 1519 write("<00000000000000000000000000000000>");
1500 1520 } else {
1501   - if (linearization_pass == 0 && deterministic_id) {
  1521 + if (linearization_pass == 0 && cfg.deterministic_id()) {
1502 1522 computeDeterministicIDData();
1503 1523 }
1504 1524 generateID(encryption.get());
... ... @@ -1517,7 +1537,7 @@ QPDFWriter::Members::writeTrailer(
1517 1537 }
1518 1538  
1519 1539 bool
1520   -QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream)
  1540 +impl::Writer::will_filter_stream(QPDFObjectHandle stream)
1521 1541 {
1522 1542 std::string s;
1523 1543 [[maybe_unused]] auto [filter, ignore1, ignore2] = will_filter_stream(stream, &s);
... ... @@ -1525,23 +1545,23 @@ QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream)
1525 1545 }
1526 1546  
1527 1547 std::tuple<const bool, const bool, const bool>
1528   -QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream, std::string* stream_data)
  1548 +impl::Writer::will_filter_stream(QPDFObjectHandle stream, std::string* stream_data)
1529 1549 {
1530 1550 const bool is_root_metadata = stream.isRootMetadata();
1531 1551 bool filter = false;
1532   - auto decode_level = stream_decode_level;
  1552 + auto decode_level = cfg.decode_level();
1533 1553 int encode_flags = 0;
1534 1554 Dictionary stream_dict = stream.getDict();
1535 1555  
1536 1556 if (stream.getFilterOnWrite()) {
1537   - filter = stream.isDataModified() || compress_streams || decode_level != qpdf_dl_none;
1538   - if (compress_streams) {
  1557 + filter = stream.isDataModified() || cfg.compress_streams() || decode_level != qpdf_dl_none;
  1558 + if (cfg.compress_streams()) {
1539 1559 // Don't filter if the stream is already compressed with FlateDecode. This way we don't
1540 1560 // make it worse if the original file used a better Flate algorithm, and we don't spend
1541 1561 // time and CPU cycles uncompressing and recompressing stuff. This can be overridden
1542 1562 // with setRecompressFlate(true).
1543 1563 Name Filter = stream_dict["/Filter"];
1544   - if (Filter && !recompress_flate && !stream.isDataModified() &&
  1564 + if (Filter && !cfg.recompress_flate() && !stream.isDataModified() &&
1545 1565 (Filter == "/FlateDecode" || Filter == "/Fl")) {
1546 1566 filter = false;
1547 1567 }
... ... @@ -1549,10 +1569,10 @@ QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream, std::string* st
1549 1569 if (is_root_metadata && (!encryption || !encryption->getEncryptMetadata())) {
1550 1570 filter = true;
1551 1571 decode_level = qpdf_dl_all;
1552   - } else if (normalize_content && normalized_streams.contains(stream)) {
  1572 + } else if (cfg.normalize_content() && normalized_streams.contains(stream)) {
1553 1573 encode_flags = qpdf_ef_normalize;
1554 1574 filter = true;
1555   - } else if (filter && compress_streams) {
  1575 + } else if (filter && cfg.compress_streams()) {
1556 1576 encode_flags = qpdf_ef_compress;
1557 1577 }
1558 1578 }
... ... @@ -1598,7 +1618,7 @@ QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream, std::string* st
1598 1618 }
1599 1619  
1600 1620 void
1601   -QPDFWriter::Members::unparseObject(
  1621 +impl::Writer::unparseObject(
1602 1622 QPDFObjectHandle object, size_t level, int flags, size_t stream_length, bool compress)
1603 1623 {
1604 1624 QPDFObjGen old_og = object.getObjGen();
... ... @@ -1606,11 +1626,11 @@ QPDFWriter::Members::unparseObject(
1606 1626 // For non-qdf, "indent" and "indent_large" are a single space between tokens. For qdf, they
1607 1627 // include the preceding newline.
1608 1628 std::string indent_large = " ";
1609   - if (qdf_mode) {
  1629 + if (cfg.qdf()) {
1610 1630 indent_large.append(2 * (level + 1), ' ');
1611 1631 indent_large[0] = '\n';
1612 1632 }
1613   - std::string_view indent{indent_large.data(), qdf_mode ? indent_large.size() - 2 : 1};
  1633 + std::string_view indent{indent_large.data(), cfg.qdf() ? indent_large.size() - 2 : 1};
1614 1634  
1615 1635 if (auto const tc = object.getTypeCode(); tc == ::ot_array) {
1616 1636 // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the
... ... @@ -1666,7 +1686,7 @@ QPDFWriter::Members::unparseObject(
1666 1686 if (need_extensions_adbe) {
1667 1687 if (!(have_extensions_other || have_extensions_adbe)) {
1668 1688 // We need Extensions and don't have it. Create it here.
1669   - QTC::TC("qpdf", "QPDFWriter create Extensions", qdf_mode ? 0 : 1);
  1689 + QTC::TC("qpdf", "QPDFWriter create Extensions", cfg.qdf() ? 0 : 1);
1670 1690 extensions = object.replaceKeyAndGetNew(
1671 1691 "/Extensions", QPDFObjectHandle::newDictionary());
1672 1692 }
... ... @@ -1771,7 +1791,7 @@ QPDFWriter::Members::unparseObject(
1771 1791 if (flags & f_stream) {
1772 1792 write(indent_large).write("/Length ");
1773 1793  
1774   - if (direct_stream_lengths) {
  1794 + if (cfg.direct_stream_lengths()) {
1775 1795 write(stream_length);
1776 1796 } else {
1777 1797 write(cur_stream_length_id).write(" 0 R");
... ... @@ -1784,7 +1804,7 @@ QPDFWriter::Members::unparseObject(
1784 1804 write(indent).write(">>");
1785 1805 } else if (tc == ::ot_stream) {
1786 1806 // Write stream data to a buffer.
1787   - if (!direct_stream_lengths) {
  1807 + if (!cfg.direct_stream_lengths()) {
1788 1808 cur_stream_length_id = obj[old_og].renumber + 1;
1789 1809 }
1790 1810  
... ... @@ -1805,14 +1825,14 @@ QPDFWriter::Members::unparseObject(
1805 1825 unparseObject(stream_dict, 0, flags, cur_stream_length, compress_stream);
1806 1826 char last_char = stream_data.empty() ? '\0' : stream_data.back();
1807 1827 write("\nstream\n").write_encrypted(stream_data);
1808   - added_newline = newline_before_endstream || (qdf_mode && last_char != '\n');
  1828 + added_newline = cfg.newline_before_endstream() || (cfg.qdf() && last_char != '\n');
1809 1829 write(added_newline ? "\nendstream" : "endstream");
1810 1830 } else if (tc == ::ot_string) {
1811 1831 std::string val;
1812 1832 if (encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) &&
1813 1833 !cur_data_key.empty()) {
1814 1834 val = object.getStringValue();
1815   - if (encrypt_use_aes) {
  1835 + if (cfg.encrypt_use_aes()) {
1816 1836 Pl_Buffer bufpl("encrypted string");
1817 1837 Pl_AES_PDF pl("aes encrypt string", &bufpl, true, cur_data_key);
1818 1838 pl.writeString(val);
... ... @@ -1841,7 +1861,7 @@ QPDFWriter::Members::unparseObject(
1841 1861 }
1842 1862  
1843 1863 void
1844   -QPDFWriter::Members::writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj)
  1864 +impl::Writer::writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj)
1845 1865 {
1846 1866 qpdf_assert_debug(first_obj > 0);
1847 1867 bool is_first = true;
... ... @@ -1860,7 +1880,7 @@ QPDFWriter::Members::writeObjectStreamOffsets(std::vector&lt;qpdf_offset_t&gt;&amp; offset
1860 1880 }
1861 1881  
1862 1882 void
1863   -QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object)
  1883 +impl::Writer::writeObjectStream(QPDFObjectHandle object)
1864 1884 {
1865 1885 // Note: object might be null if this is a place-holder for an object stream that we are
1866 1886 // generating from scratch.
... ... @@ -1878,7 +1898,7 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object)
1878 1898 std::string stream_buffer_pass1;
1879 1899 std::string stream_buffer_pass2;
1880 1900 int first_obj = -1;
1881   - const bool compressed = compress_streams && !qdf_mode;
  1901 + const bool compressed = cfg.compress_streams() && !cfg.qdf();
1882 1902 {
1883 1903 // Pass 1
1884 1904 auto pp_ostream_pass1 = pipeline_stack.activate(stream_buffer_pass1);
... ... @@ -1890,9 +1910,9 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object)
1890 1910 if (first_obj == -1) {
1891 1911 first_obj = new_o;
1892 1912 }
1893   - if (qdf_mode) {
  1913 + if (cfg.qdf()) {
1894 1914 write("%% Object stream: object ").write(new_o).write(", index ").write(count);
1895   - if (!suppress_original_object_ids) {
  1915 + if (!cfg.no_original_object_ids()) {
1896 1916 write("; original object ID: ").write(og.getObj());
1897 1917 // For compatibility, only write the generation if non-zero. While object
1898 1918 // streams only allow objects with generation 0, if we are generating object
... ... @@ -1968,16 +1988,15 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object)
1968 1988 }
1969 1989 }
1970 1990 write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n").write_encrypted(stream_buffer_pass2);
  1991 + write(cfg.newline_before_endstream() ? "\nendstream" : "endstream");
1971 1992 if (encryption) {
1972   - QTC::TC("qpdf", "QPDFWriter encrypt object stream");
  1993 + cur_data_key.clear();
1973 1994 }
1974   - write(newline_before_endstream ? "\nendstream" : "endstream");
1975   - cur_data_key.clear();
1976 1995 closeObject(new_stream_id);
1977 1996 }
1978 1997  
1979 1998 void
1980   -QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_index)
  1999 +impl::Writer::writeObject(QPDFObjectHandle object, int object_stream_index)
1981 2000 {
1982 2001 QPDFObjGen old_og = object.getObjGen();
1983 2002  
... ... @@ -1989,7 +2008,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde
1989 2008  
1990 2009 indicateProgress(false, false);
1991 2010 auto new_id = obj[old_og].renumber;
1992   - if (qdf_mode) {
  2011 + if (cfg.qdf()) {
1993 2012 if (page_object_to_seq.contains(old_og)) {
1994 2013 write("%% Page ").write(page_object_to_seq[old_og]).write("\n");
1995 2014 }
... ... @@ -1998,7 +2017,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde
1998 2017 }
1999 2018 }
2000 2019 if (object_stream_index == -1) {
2001   - if (qdf_mode && !suppress_original_object_ids) {
  2020 + if (cfg.qdf() && !cfg.no_original_object_ids()) {
2002 2021 write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n");
2003 2022 }
2004 2023 openObject(new_id);
... ... @@ -2011,8 +2030,8 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde
2011 2030 write("\n");
2012 2031 }
2013 2032  
2014   - if (!direct_stream_lengths && object.isStream()) {
2015   - if (qdf_mode) {
  2033 + if (!cfg.direct_stream_lengths() && object.isStream()) {
  2034 + if (cfg.qdf()) {
2016 2035 if (added_newline) {
2017 2036 write("%QDF: ignore_newline\n");
2018 2037 }
... ... @@ -2024,7 +2043,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde
2024 2043 }
2025 2044  
2026 2045 std::string
2027   -QPDFWriter::Members::getOriginalID1()
  2046 +impl::Writer::getOriginalID1()
2028 2047 {
2029 2048 QPDFObjectHandle trailer = qpdf.getTrailer();
2030 2049 if (trailer.hasKey("/ID")) {
... ... @@ -2035,7 +2054,7 @@ QPDFWriter::Members::getOriginalID1()
2035 2054 }
2036 2055  
2037 2056 void
2038   -QPDFWriter::Members::generateID(bool encrypted)
  2057 +impl::Writer::generateID(bool encrypted)
2039 2058 {
2040 2059 // Generate the ID lazily so that we can handle the user's preference to use static or
2041 2060 // deterministic ID generation.
... ... @@ -2048,7 +2067,7 @@ QPDFWriter::Members::generateID(bool encrypted)
2048 2067  
2049 2068 std::string result;
2050 2069  
2051   - if (static_id) {
  2070 + if (cfg.static_id()) {
2052 2071 // For test suite use only...
2053 2072 static unsigned char tmp[] = {
2054 2073 0x31,
... ... @@ -2080,7 +2099,7 @@ QPDFWriter::Members::generateID(bool encrypted)
2080 2099 // that case, would have the same ID regardless of the output file's name.
2081 2100  
2082 2101 std::string seed;
2083   - if (deterministic_id) {
  2102 + if (cfg.deterministic_id()) {
2084 2103 if (encrypted) {
2085 2104 throw std::runtime_error(
2086 2105 "QPDFWriter: unable to generated a deterministic ID because the file to be "
... ... @@ -2125,7 +2144,7 @@ QPDFWriter::Members::generateID(bool encrypted)
2125 2144 }
2126 2145  
2127 2146 void
2128   -QPDFWriter::Members::initializeSpecialStreams()
  2147 +impl::Writer::initializeSpecialStreams()
2129 2148 {
2130 2149 // Mark all page content streams in case we are filtering or normalizing.
2131 2150 int num = 0;
... ... @@ -2150,9 +2169,9 @@ QPDFWriter::Members::initializeSpecialStreams()
2150 2169 }
2151 2170  
2152 2171 void
2153   -QPDFWriter::Members::preserveObjectStreams()
  2172 +impl::Writer::preserveObjectStreams()
2154 2173 {
2155   - auto const& xref = getXRefTable();
  2174 + auto const& xref = objects.xref_table();
2156 2175 // Our object_to_object_stream map has to map ObjGen -> ObjGen since we may be generating object
2157 2176 // streams out of old objects that have generation numbers greater than zero. However in an
2158 2177 // existing PDF, all object stream objects and all objects in them must have generation 0
... ... @@ -2162,7 +2181,7 @@ QPDFWriter::Members::preserveObjectStreams()
2162 2181 // objects from being included.
2163 2182 auto end = xref.cend();
2164 2183 obj.streams_empty = true;
2165   - if (preserve_unreferenced_objects) {
  2184 + if (cfg.preserve_unreferenced()) {
2166 2185 for (auto iter = xref.cbegin(); iter != end; ++iter) {
2167 2186 if (iter->second.getType() == 2) {
2168 2187 // Pdf contains object streams.
... ... @@ -2177,9 +2196,9 @@ QPDFWriter::Members::preserveObjectStreams()
2177 2196 if (iter->second.getType() == 2) {
2178 2197 // Pdf contains object streams.
2179 2198 obj.streams_empty = false;
2180   - auto eligible = getCompressibleObjSet();
  2199 + auto eligible = objects.compressible_set();
2181 2200 // The object pointed to by iter may be a previous generation, in which case it is
2182   - // removed by getCompressibleObjSet. We need to restart the loop (while the object
  2201 + // removed by compressible_set. We need to restart the loop (while the object
2183 2202 // table may contain multiple generations of an object).
2184 2203 for (iter = xref.cbegin(); iter != end; ++iter) {
2185 2204 if (iter->second.getType() == 2) {
... ... @@ -2198,7 +2217,7 @@ QPDFWriter::Members::preserveObjectStreams()
2198 2217 }
2199 2218  
2200 2219 void
2201   -QPDFWriter::Members::generateObjectStreams()
  2220 +impl::Writer::generateObjectStreams()
2202 2221 {
2203 2222 // Basic strategy: make a list of objects that can go into an object stream. Then figure out
2204 2223 // how many object streams are needed so that we can distribute objects approximately evenly
... ... @@ -2208,7 +2227,7 @@ QPDFWriter::Members::generateObjectStreams()
2208 2227  
2209 2228 // This code doesn't do anything with /Extends.
2210 2229  
2211   - std::vector<QPDFObjGen> eligible = getCompressibleObjGens();
  2230 + auto eligible = objects.compressible_vector();
2212 2231 size_t n_object_streams = (eligible.size() + 99U) / 100U;
2213 2232  
2214 2233 initializeTables(2U * n_object_streams);
... ... @@ -2237,7 +2256,7 @@ QPDFWriter::Members::generateObjectStreams()
2237 2256 }
2238 2257  
2239 2258 Dictionary
2240   -QPDFWriter::Members::trimmed_trailer()
  2259 +impl::Writer::trimmed_trailer()
2241 2260 {
2242 2261 // Remove keys from the trailer that necessarily have to be replaced when writing the file.
2243 2262  
... ... @@ -2264,7 +2283,7 @@ QPDFWriter::Members::trimmed_trailer()
2264 2283  
2265 2284 // Make document extension level information direct as required by the spec.
2266 2285 void
2267   -QPDFWriter::Members::prepareFileForWrite()
  2286 +impl::Writer::prepareFileForWrite()
2268 2287 {
2269 2288 qpdf.fixDanglingReferences();
2270 2289 auto root = qpdf.getRoot();
... ... @@ -2287,15 +2306,15 @@ QPDFWriter::Members::prepareFileForWrite()
2287 2306 }
2288 2307  
2289 2308 void
2290   -QPDFWriter::Members::initializeTables(size_t extra)
  2309 +impl::Writer::initializeTables(size_t extra)
2291 2310 {
2292   - auto size = QIntC::to_size(tableSize() + 100) + extra;
  2311 + auto size = objects.table_size() + 100u + extra;
2293 2312 obj.resize(size);
2294 2313 new_obj.resize(size);
2295 2314 }
2296 2315  
2297 2316 void
2298   -QPDFWriter::Members::doWriteSetup()
  2317 +impl::Writer::doWriteSetup()
2299 2318 {
2300 2319 if (did_write_setup) {
2301 2320 return;
... ... @@ -2304,63 +2323,42 @@ QPDFWriter::Members::doWriteSetup()
2304 2323  
2305 2324 // Do preliminary setup
2306 2325  
2307   - if (linearized) {
2308   - qdf_mode = false;
  2326 + if (cfg.linearize()) {
  2327 + cfg.qdf(false);
2309 2328 }
2310 2329  
2311   - if (pclm) {
2312   - stream_decode_level = qpdf_dl_none;
2313   - compress_streams = false;
  2330 + if (cfg.pclm()) {
2314 2331 encryption = nullptr;
2315 2332 }
2316 2333  
2317   - if (qdf_mode) {
2318   - if (!normalize_content_set) {
2319   - normalize_content = true;
2320   - }
2321   - if (!compress_streams_set) {
2322   - compress_streams = false;
2323   - }
2324   - if (!stream_decode_level_set) {
2325   - stream_decode_level = qpdf_dl_generalized;
2326   - }
2327   - }
2328   -
2329 2334 if (encryption) {
2330 2335 // Encryption has been explicitly set
2331   - preserve_encryption = false;
2332   - } else if (normalize_content || pclm || qdf_mode) {
  2336 + cfg.preserve_encryption(false);
  2337 + } else if (cfg.normalize_content() || cfg.pclm()) {
2333 2338 // Encryption makes looking at contents pretty useless. If the user explicitly encrypted
2334 2339 // though, we still obey that.
2335   - preserve_encryption = false;
  2340 + cfg.preserve_encryption(false);
2336 2341 }
2337 2342  
2338   - if (preserve_encryption) {
  2343 + if (cfg.preserve_encryption()) {
2339 2344 copyEncryptionParameters(qpdf);
2340 2345 }
2341 2346  
2342   - if (!forced_pdf_version.empty()) {
  2347 + if (!cfg.forced_pdf_version().empty()) {
2343 2348 int major = 0;
2344 2349 int minor = 0;
2345   - parseVersion(forced_pdf_version, major, minor);
2346   - disableIncompatibleEncryption(major, minor, forced_extension_level);
  2350 + parseVersion(cfg.forced_pdf_version(), major, minor);
  2351 + disableIncompatibleEncryption(major, minor, cfg.forced_extension_level());
2347 2352 if (compareVersions(major, minor, 1, 5) < 0) {
2348   - object_stream_mode = qpdf_o_disable;
  2353 + cfg.object_streams(qpdf_o_disable);
2349 2354 }
2350 2355 }
2351 2356  
2352   - if (qdf_mode || normalize_content) {
  2357 + if (cfg.qdf() || cfg.normalize_content()) {
2353 2358 initializeSpecialStreams();
2354 2359 }
2355 2360  
2356   - if (qdf_mode) {
2357   - // Generate indirect stream lengths for qdf mode since fix-qdf uses them for storing
2358   - // recomputed stream length data. Certain streams such as object streams, xref streams, and
2359   - // hint streams always get direct stream lengths.
2360   - direct_stream_lengths = false;
2361   - }
2362   -
2363   - switch (object_stream_mode) {
  2361 + switch (cfg.object_streams()) {
2364 2362 case qpdf_o_disable:
2365 2363 initializeTables();
2366 2364 obj.streams_empty = true;
... ... @@ -2374,12 +2372,10 @@ QPDFWriter::Members::doWriteSetup()
2374 2372 case qpdf_o_generate:
2375 2373 generateObjectStreams();
2376 2374 break;
2377   -
2378   - // no default so gcc will warn for missing case tag
2379 2375 }
2380 2376  
2381 2377 if (!obj.streams_empty) {
2382   - if (linearized) {
  2378 + if (cfg.linearize()) {
2383 2379 // Page dictionaries are not allowed to be compressed objects.
2384 2380 for (auto& page: pages) {
2385 2381 if (obj[page].object_stream > 0) {
... ... @@ -2388,9 +2384,9 @@ QPDFWriter::Members::doWriteSetup()
2388 2384 }
2389 2385 }
2390 2386  
2391   - if (linearized || encryption) {
2392   - // The document catalog is not allowed to be compressed in linearized files either. It
2393   - // also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to
  2387 + if (cfg.linearize() || encryption) {
  2388 + // The document catalog is not allowed to be compressed in cfg.linearized_ files either.
  2389 + // It also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to
2394 2390 // handle encrypted files with compressed document catalogs, so we disable them in that
2395 2391 // case as well.
2396 2392 if (obj[root_og].object_stream > 0) {
... ... @@ -2413,16 +2409,16 @@ QPDFWriter::Members::doWriteSetup()
2413 2409 if (object_stream_to_objects.empty()) {
2414 2410 obj.streams_empty = true;
2415 2411 } else {
2416   - w.setMinimumPDFVersion("1.5");
  2412 + setMinimumPDFVersion("1.5");
2417 2413 }
2418 2414 }
2419 2415  
2420 2416 setMinimumPDFVersion(qpdf.getPDFVersion(), qpdf.getExtensionLevel());
2421 2417 final_pdf_version = min_pdf_version;
2422 2418 final_extension_level = min_extension_level;
2423   - if (!forced_pdf_version.empty()) {
2424   - final_pdf_version = forced_pdf_version;
2425   - final_extension_level = forced_extension_level;
  2419 + if (!cfg.forced_pdf_version().empty()) {
  2420 + final_pdf_version = cfg.forced_pdf_version();
  2421 + final_extension_level = cfg.forced_extension_level();
2426 2422 }
2427 2423 }
2428 2424  
... ... @@ -2433,17 +2429,17 @@ QPDFWriter::write()
2433 2429 }
2434 2430  
2435 2431 void
2436   -QPDFWriter::Members::write()
  2432 +impl::Writer::write()
2437 2433 {
2438 2434 doWriteSetup();
2439 2435  
2440 2436 // Set up progress reporting. For linearized files, we write two passes. events_expected is an
2441 2437 // approximation, but it's good enough for progress reporting, which is mostly a guess anyway.
2442   - events_expected = QIntC::to_int(qpdf.getObjectCount() * (linearized ? 2 : 1));
  2438 + events_expected = QIntC::to_int(qpdf.getObjectCount() * (cfg.linearize() ? 2 : 1));
2443 2439  
2444 2440 prepareFileForWrite();
2445 2441  
2446   - if (linearized) {
  2442 + if (cfg.linearize()) {
2447 2443 writeLinearized();
2448 2444 } else {
2449 2445 writeStandard();
... ... @@ -2474,7 +2470,7 @@ QPDFWriter::getWrittenXRefTable()
2474 2470 }
2475 2471  
2476 2472 std::map<QPDFObjGen, QPDFXRefEntry>
2477   -QPDFWriter::Members::getWrittenXRefTable()
  2473 +impl::Writer::getWrittenXRefTable()
2478 2474 {
2479 2475 std::map<QPDFObjGen, QPDFXRefEntry> result;
2480 2476  
... ... @@ -2488,7 +2484,7 @@ QPDFWriter::Members::getWrittenXRefTable()
2488 2484 }
2489 2485  
2490 2486 void
2491   -QPDFWriter::Members::enqueuePart(std::vector<QPDFObjectHandle>& part)
  2487 +impl::Writer::enqueuePart(std::vector<QPDFObjectHandle>& part)
2492 2488 {
2493 2489 for (auto const& oh: part) {
2494 2490 enqueue(oh);
... ... @@ -2496,7 +2492,7 @@ QPDFWriter::Members::enqueuePart(std::vector&lt;QPDFObjectHandle&gt;&amp; part)
2496 2492 }
2497 2493  
2498 2494 void
2499   -QPDFWriter::Members::writeEncryptionDictionary()
  2495 +impl::Writer::writeEncryptionDictionary()
2500 2496 {
2501 2497 encryption_dict_objid = openObject(encryption_dict_objid);
2502 2498 auto& enc = *encryption;
... ... @@ -2505,10 +2501,10 @@ QPDFWriter::Members::writeEncryptionDictionary()
2505 2501 write("<<");
2506 2502 if (V >= 4) {
2507 2503 write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");
2508   - write(encrypt_use_aes ? (V < 5 ? "/AESV2" : "/AESV3") : "/V2");
  2504 + write(cfg.encrypt_use_aes() ? (V < 5 ? "/AESV2" : "/AESV3") : "/V2");
2509 2505 // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of
2510 2506 // MacOS won't open encrypted files without it.
2511   - write((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>");
  2507 + write(V < 5 ? " /Length 16 >> >>" : " /Length 32 >> >>");
2512 2508 if (!encryption->getEncryptMetadata()) {
2513 2509 write(" /EncryptMetadata false");
2514 2510 }
... ... @@ -2543,10 +2539,10 @@ QPDFWriter::getFinalVersion()
2543 2539 }
2544 2540  
2545 2541 void
2546   -QPDFWriter::Members::writeHeader()
  2542 +impl::Writer::writeHeader()
2547 2543 {
2548 2544 write("%PDF-").write(final_pdf_version);
2549   - if (pclm) {
  2545 + if (cfg.pclm()) {
2550 2546 // PCLm version
2551 2547 write("\n%PCLm 1.0\n");
2552 2548 } else {
... ... @@ -2563,13 +2559,13 @@ QPDFWriter::Members::writeHeader()
2563 2559 }
2564 2560  
2565 2561 void
2566   -QPDFWriter::Members::writeHintStream(int hint_id)
  2562 +impl::Writer::writeHintStream(int hint_id)
2567 2563 {
2568 2564 std::string hint_buffer;
2569 2565 int S = 0;
2570 2566 int O = 0;
2571   - bool compressed = compress_streams;
2572   - generateHintStream(new_obj, obj, hint_buffer, S, O, compressed);
  2567 + bool compressed = cfg.compress_streams();
  2568 + lin.generateHintStream(new_obj, obj, hint_buffer, S, O, compressed);
2573 2569  
2574 2570 openObject(hint_id);
2575 2571 setDataKey(hint_id);
... ... @@ -2597,7 +2593,7 @@ QPDFWriter::Members::writeHintStream(int hint_id)
2597 2593 }
2598 2594  
2599 2595 qpdf_offset_t
2600   -QPDFWriter::Members::writeXRefTable(trailer_e which, int first, int last, int size)
  2596 +impl::Writer::writeXRefTable(trailer_e which, int first, int last, int size)
2601 2597 {
2602 2598 // There are too many extra arguments to replace overloaded function with defaults in the header
2603 2599 // file...too much risk of leaving something off.
... ... @@ -2605,7 +2601,7 @@ QPDFWriter::Members::writeXRefTable(trailer_e which, int first, int last, int si
2605 2601 }
2606 2602  
2607 2603 qpdf_offset_t
2608   -QPDFWriter::Members::writeXRefTable(
  2604 +impl::Writer::writeXRefTable(
2609 2605 trailer_e which,
2610 2606 int first,
2611 2607 int last,
... ... @@ -2640,7 +2636,7 @@ QPDFWriter::Members::writeXRefTable(
2640 2636 }
2641 2637  
2642 2638 qpdf_offset_t
2643   -QPDFWriter::Members::writeXRefStream(
  2639 +impl::Writer::writeXRefStream(
2644 2640 int objid, int max_id, qpdf_offset_t max_offset, trailer_e which, int first, int last, int size)
2645 2641 {
2646 2642 // There are too many extra arguments to replace overloaded function with defaults in the header
... ... @@ -2650,7 +2646,7 @@ QPDFWriter::Members::writeXRefStream(
2650 2646 }
2651 2647  
2652 2648 qpdf_offset_t
2653   -QPDFWriter::Members::writeXRefStream(
  2649 +impl::Writer::writeXRefStream(
2654 2650 int xref_id,
2655 2651 int max_id,
2656 2652 qpdf_offset_t max_offset,
... ... @@ -2681,7 +2677,7 @@ QPDFWriter::Members::writeXRefStream(
2681 2677 new_obj[xref_id].xref = QPDFXRefEntry(pipeline->getCount());
2682 2678  
2683 2679 std::string xref_data;
2684   - const bool compressed = compress_streams && !qdf_mode;
  2680 + const bool compressed = cfg.compress_streams() && !cfg.qdf();
2685 2681 {
2686 2682 auto pp_xref = pipeline_stack.activate(xref_data);
2687 2683  
... ... @@ -2746,7 +2742,7 @@ QPDFWriter::Members::writeXRefStream(
2746 2742 }
2747 2743  
2748 2744 size_t
2749   -QPDFWriter::Members::calculateXrefStreamPadding(qpdf_offset_t xref_bytes)
  2745 +impl::Writer::calculateXrefStreamPadding(qpdf_offset_t xref_bytes)
2750 2746 {
2751 2747 // This routine is called right after a linearization first pass xref stream has been written
2752 2748 // 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)
2758 2754 }
2759 2755  
2760 2756 void
2761   -QPDFWriter::Members::writeLinearized()
  2757 +impl::Writer::writeLinearized()
2762 2758 {
2763 2759 // Optimize file and enqueue objects in order
2764 2760  
... ... @@ -2772,14 +2768,14 @@ QPDFWriter::Members::writeLinearized()
2772 2768 }
2773 2769 };
2774 2770  
2775   - optimize(obj, skip_stream_parameters);
  2771 + lin.optimize(obj, skip_stream_parameters);
2776 2772  
2777 2773 std::vector<QPDFObjectHandle> part4;
2778 2774 std::vector<QPDFObjectHandle> part6;
2779 2775 std::vector<QPDFObjectHandle> part7;
2780 2776 std::vector<QPDFObjectHandle> part8;
2781 2777 std::vector<QPDFObjectHandle> part9;
2782   - getLinearizedParts(obj, part4, part6, part7, part8, part9);
  2778 + lin.parts(obj, part4, part6, part7, part8, part9);
2783 2779  
2784 2780 // Object number sequence:
2785 2781 //
... ... @@ -2871,7 +2867,7 @@ QPDFWriter::Members::writeLinearized()
2871 2867 enqueuePart(part8);
2872 2868 enqueuePart(part9);
2873 2869 if (next_objid != after_second_half) {
2874   - throw std::runtime_error("error encountered after writing part 9 of linearized data");
  2870 + throw std::runtime_error("error encountered after writing part 9 of cfg.linearized_ data");
2875 2871 }
2876 2872  
2877 2873 qpdf_offset_t hint_length = 0;
... ... @@ -2884,15 +2880,15 @@ QPDFWriter::Members::writeLinearized()
2884 2880 auto pp_md5 = pipeline_stack.popper();
2885 2881 for (int pass: {1, 2}) {
2886 2882 if (pass == 1) {
2887   - if (!lin_pass1_filename.empty()) {
2888   - lin_pass1_file = QUtil::safe_fopen(lin_pass1_filename.c_str(), "wb");
  2883 + if (!cfg.linearize_pass1().empty()) {
  2884 + lin_pass1_file = QUtil::safe_fopen(cfg.linearize_pass1().data(), "wb");
2889 2885 pipeline_stack.activate(
2890 2886 pp_pass1,
2891 2887 std::make_unique<Pl_StdioFile>("linearization pass1", lin_pass1_file));
2892 2888 } else {
2893 2889 pipeline_stack.activate(pp_pass1, true);
2894 2890 }
2895   - if (deterministic_id) {
  2891 + if (cfg.deterministic_id()) {
2896 2892 pipeline_stack.activate_md5(pp_md5);
2897 2893 }
2898 2894 }
... ... @@ -2927,7 +2923,7 @@ QPDFWriter::Members::writeLinearized()
2927 2923  
2928 2924 // If the user supplied any additional header text, write it here after the linearization
2929 2925 // parameter dictionary.
2930   - write(extra_header_text);
  2926 + write(cfg.extra_header_text());
2931 2927  
2932 2928 // Part 3: first page cross reference table and trailer.
2933 2929  
... ... @@ -3062,7 +3058,7 @@ QPDFWriter::Members::writeLinearized()
3062 3058 write("startxref\n").write(first_xref_offset).write("\n%%EOF\n");
3063 3059  
3064 3060 if (pass == 1) {
3065   - if (deterministic_id) {
  3061 + if (cfg.deterministic_id()) {
3066 3062 QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1);
3067 3063 computeDeterministicIDData();
3068 3064 pp_md5.pop();
... ... @@ -3105,9 +3101,9 @@ QPDFWriter::Members::writeLinearized()
3105 3101 }
3106 3102  
3107 3103 void
3108   -QPDFWriter::Members::enqueueObjectsStandard()
  3104 +impl::Writer::enqueueObjectsStandard()
3109 3105 {
3110   - if (preserve_unreferenced_objects) {
  3106 + if (cfg.preserve_unreferenced()) {
3111 3107 for (auto const& oh: qpdf.getAllObjects()) {
3112 3108 enqueue(oh);
3113 3109 }
... ... @@ -3127,7 +3123,7 @@ QPDFWriter::Members::enqueueObjectsStandard()
3127 3123 }
3128 3124  
3129 3125 void
3130   -QPDFWriter::Members::enqueueObjectsPCLm()
  3126 +impl::Writer::enqueueObjectsPCLm()
3131 3127 {
3132 3128 // Image transform stream content for page strip images. Each of this new stream has to come
3133 3129 // after every page image strip written in the pclm file.
... ... @@ -3151,7 +3147,7 @@ QPDFWriter::Members::enqueueObjectsPCLm()
3151 3147 }
3152 3148  
3153 3149 void
3154   -QPDFWriter::Members::indicateProgress(bool decrement, bool finished)
  3150 +impl::Writer::indicateProgress(bool decrement, bool finished)
3155 3151 {
3156 3152 if (decrement) {
3157 3153 --events_seen;
... ... @@ -3185,19 +3181,19 @@ QPDFWriter::registerProgressReporter(std::shared_ptr&lt;ProgressReporter&gt; pr)
3185 3181 }
3186 3182  
3187 3183 void
3188   -QPDFWriter::Members::writeStandard()
  3184 +impl::Writer::writeStandard()
3189 3185 {
3190 3186 auto pp_md5 = pipeline_stack.popper();
3191   - if (deterministic_id) {
  3187 + if (cfg.deterministic_id()) {
3192 3188 pipeline_stack.activate_md5(pp_md5);
3193 3189 }
3194 3190  
3195 3191 // Start writing
3196 3192  
3197 3193 writeHeader();
3198   - write(extra_header_text);
  3194 + write(cfg.extra_header_text());
3199 3195  
3200   - if (pclm) {
  3196 + if (cfg.pclm()) {
3201 3197 enqueueObjectsPCLm();
3202 3198 } else {
3203 3199 enqueueObjectsStandard();
... ... @@ -3227,7 +3223,7 @@ QPDFWriter::Members::writeStandard()
3227 3223 }
3228 3224 write("startxref\n").write(xref_offset).write("\n%%EOF\n");
3229 3225  
3230   - if (deterministic_id) {
  3226 + if (cfg.deterministic_id()) {
3231 3227 QTC::TC(
3232 3228 "qpdf",
3233 3229 "QPDFWriter standard deterministic ID",
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -1681,7 +1681,7 @@ Lin::pushOutlinesToPart(
1681 1681 }
1682 1682  
1683 1683 void
1684   -Lin::getLinearizedParts(
  1684 +Lin::parts(
1685 1685 QPDFWriter::ObjTable const& obj,
1686 1686 std::vector<QPDFObjectHandle>& part4,
1687 1687 std::vector<QPDFObjectHandle>& part6,
... ...
libqpdf/QPDF_objects.cc
... ... @@ -1916,7 +1916,7 @@ QPDF::swapObjects(QPDFObjGen og1, QPDFObjGen og2)
1916 1916 }
1917 1917  
1918 1918 size_t
1919   -Objects::tableSize()
  1919 +Objects::table_size()
1920 1920 {
1921 1921 // If obj_cache is dense, accommodate all object in tables,else accommodate only original
1922 1922 // objects.
... ... @@ -1936,20 +1936,20 @@ Objects::tableSize()
1936 1936 }
1937 1937  
1938 1938 std::vector<QPDFObjGen>
1939   -Objects::getCompressibleObjVector()
  1939 +Objects::compressible_vector()
1940 1940 {
1941   - return getCompressibleObjGens<QPDFObjGen>();
  1941 + return compressible<QPDFObjGen>();
1942 1942 }
1943 1943  
1944 1944 std::vector<bool>
1945   -Objects::getCompressibleObjSet()
  1945 +Objects::compressible_set()
1946 1946 {
1947   - return getCompressibleObjGens<bool>();
  1947 + return compressible<bool>();
1948 1948 }
1949 1949  
1950 1950 template <typename T>
1951 1951 std::vector<T>
1952   -Objects::getCompressibleObjGens()
  1952 +Objects::compressible()
1953 1953 {
1954 1954 // Return a list of objects that are allowed to be in object streams. Walk through the objects
1955 1955 // by traversing the document from the root, including a traversal of the pages tree. This
... ... @@ -1969,10 +1969,9 @@ Objects::getCompressibleObjGens()
1969 1969 std::vector<T> result;
1970 1970 if constexpr (std::is_same_v<T, QPDFObjGen>) {
1971 1971 result.reserve(m->obj_cache.size());
1972   - } else if constexpr (std::is_same_v<T, bool>) {
1973   - result.resize(max_obj + 1U, false);
1974 1972 } else {
1975   - throw std::logic_error("Unsupported type in QPDF::getCompressibleObjGens");
  1973 + qpdf_static_expect(std::is_same_v<T, bool>);
  1974 + result.resize(max_obj + 1U, false);
1976 1975 }
1977 1976 while (!queue.empty()) {
1978 1977 auto obj = queue.back();
... ...
libqpdf/qpdf/ObjTable.hh
... ... @@ -75,7 +75,6 @@ class ObjTable: public std::vector&lt;T&gt;
75 75 return contains(static_cast<size_t>(oh.getObjectID()));
76 76 }
77 77  
78   - protected:
79 78 inline T&
80 79 operator[](int id)
81 80 {
... ...
libqpdf/qpdf/QPDFJob_private.hh
... ... @@ -5,6 +5,7 @@
5 5  
6 6 #include <qpdf/ClosedFileInputSource.hh>
7 7 #include <qpdf/QPDFLogger.hh>
  8 +#include <qpdf/QPDFWriter_private.hh>
8 9 #include <qpdf/QPDF_private.hh>
9 10  
10 11 // 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
150 151  
151 152 public:
152 153 Members(QPDFJob& job) :
153   - log(qcf.log()),
  154 + log(d_cfg.log()),
154 155 inputs(job)
155 156 {
156 157 }
... ... @@ -168,14 +169,14 @@ class QPDFJob::Members
168 169 static int constexpr DEFAULT_OI_MIN_AREA = 16384;
169 170 static int constexpr DEFAULT_II_MIN_BYTES = 1024;
170 171  
171   - qpdf::Doc::Config qcf;
  172 + qpdf::Doc::Config d_cfg;
  173 + qpdf::Writer::Config w_cfg;
172 174 std::shared_ptr<QPDFLogger> log;
173 175 std::string message_prefix{"qpdf"};
174 176 bool warnings{false};
175 177 unsigned long encryption_status{0};
176 178 bool verbose{false};
177 179 std::string password;
178   - bool linearize{false};
179 180 bool decrypt{false};
180 181 bool remove_restrictions{false};
181 182 int split_pages{0};
... ... @@ -206,25 +207,9 @@ class QPDFJob::Members
206 207 bool force_R5{false};
207 208 bool cleartext_metadata{false};
208 209 bool use_aes{false};
209   - bool stream_data_set{false};
210   - qpdf_stream_data_e stream_data_mode{qpdf_s_compress};
211   - bool compress_streams{true};
212   - bool compress_streams_set{false};
213   - bool recompress_flate{false};
214   - bool recompress_flate_set{false};
215 210 int compression_level{-1};
216 211 int jpeg_quality{-1};
217   - qpdf_stream_decode_level_e decode_level{qpdf_dl_generalized};
218   - bool decode_level_set{false};
219   - bool normalize_set{false};
220   - bool normalize{false};
221   - bool object_stream_set{false};
222   - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve};
223   - bool qdf_mode{false};
224   - bool preserve_unreferenced_objects{false};
225 212 remove_unref_e remove_unreferenced_page_resources{re_auto};
226   - bool newline_before_endstream{false};
227   - std::string linearize_pass1;
228 213 bool coalesce_contents{false};
229 214 bool flatten_annotations{false};
230 215 int flatten_annotations_required{0};
... ... @@ -234,10 +219,7 @@ class QPDFJob::Members
234 219 std::string min_version;
235 220 std::string force_version;
236 221 bool show_npages{false};
237   - bool deterministic_id{false};
238   - bool static_id{false};
239 222 bool static_aes_iv{false};
240   - bool suppress_original_object_id{false};
241 223 bool show_encryption{false};
242 224 bool show_encryption_key{false};
243 225 bool check_linearization{false};
... ...
libqpdf/qpdf/QPDFObject_private.hh
... ... @@ -30,6 +30,11 @@ namespace qpdf
30 30 class Integer;
31 31 class Name;
32 32 class Stream;
  33 +
  34 + namespace impl
  35 + {
  36 + class Writer;
  37 + }
33 38 } // namespace qpdf
34 39  
35 40 class QPDF_Array final
... ... @@ -256,7 +261,7 @@ class QPDF_String final
256 261 {
257 262 friend class QPDFObject;
258 263 friend class qpdf::BaseHandle;
259   - friend class QPDFWriter;
  264 + friend class qpdf::impl::Writer;
260 265  
261 266 public:
262 267 static std::shared_ptr<QPDFObject> create_utf16(std::string const& utf8_val);
... ...
libqpdf/qpdf/QPDFWriter_private.hh
... ... @@ -5,10 +5,332 @@
5 5  
6 6 #include <qpdf/ObjTable.hh>
7 7 #include <qpdf/Pipeline_private.hh>
  8 +#include <qpdf/QPDF.hh>
  9 +#include <qpdf/QPDFUsage.hh>
8 10  
9 11 // This file is intended for inclusion by QPDFWriter, QPDF, QPDF_optimization and QPDF_linearization
10 12 // only.
11 13  
  14 +namespace qpdf
  15 +{
  16 + namespace impl
  17 + {
  18 + class Writer;
  19 + }
  20 +
  21 + class Writer: public QPDFWriter
  22 + {
  23 + public:
  24 + class Config
  25 + {
  26 + public:
  27 + Config() = default;
  28 + Config(Config const&) = default;
  29 + Config(Config&&) = delete;
  30 + Config& operator=(Config const&) = default;
  31 + Config& operator=(Config&&) = delete;
  32 + ~Config() = default;
  33 +
  34 + Config(bool permissive) :
  35 + permissive_(permissive)
  36 + {
  37 + }
  38 +
  39 + bool
  40 + linearize() const
  41 + {
  42 + return linearize_;
  43 + }
  44 +
  45 + Config& linearize(bool val);
  46 +
  47 + std::string const&
  48 + linearize_pass1() const
  49 + {
  50 + return linearize_pass1_;
  51 + }
  52 +
  53 + Config&
  54 + linearize_pass1(std::string const& val)
  55 + {
  56 + linearize_pass1_ = val;
  57 + return *this;
  58 + }
  59 +
  60 + bool
  61 + preserve_encryption() const
  62 + {
  63 + return preserve_encryption_;
  64 + }
  65 +
  66 + Config&
  67 + preserve_encryption(bool val)
  68 + {
  69 + preserve_encryption_ = val;
  70 + return *this;
  71 + }
  72 +
  73 + bool
  74 + encrypt_use_aes() const
  75 + {
  76 + return encrypt_use_aes_;
  77 + }
  78 +
  79 + Config&
  80 + encrypt_use_aes(bool val)
  81 + {
  82 + encrypt_use_aes_ = val;
  83 + return *this;
  84 + }
  85 +
  86 + Config&
  87 + default_decode_level(qpdf_stream_decode_level_e val)
  88 + {
  89 + if (!decode_level_set_) {
  90 + decode_level_ = val;
  91 + }
  92 + return *this;
  93 + }
  94 +
  95 + qpdf_stream_decode_level_e
  96 + decode_level() const
  97 + {
  98 + return decode_level_;
  99 + }
  100 +
  101 + Config& decode_level(qpdf_stream_decode_level_e val);
  102 +
  103 + qpdf_object_stream_e
  104 + object_streams() const
  105 + {
  106 + return object_streams_;
  107 + }
  108 +
  109 + Config&
  110 + object_streams(qpdf_object_stream_e val)
  111 + {
  112 + object_streams_ = val;
  113 + return *this;
  114 + }
  115 +
  116 + bool
  117 + compress_streams() const
  118 + {
  119 + return compress_streams_;
  120 + }
  121 +
  122 + Config& compress_streams(bool val);
  123 +
  124 + bool
  125 + direct_stream_lengths() const
  126 + {
  127 + return direct_stream_lengths_;
  128 + }
  129 +
  130 + bool
  131 + newline_before_endstream() const
  132 + {
  133 + return newline_before_endstream_;
  134 + }
  135 +
  136 + Config&
  137 + newline_before_endstream(bool val)
  138 + {
  139 + newline_before_endstream_ = val;
  140 + return *this;
  141 + }
  142 +
  143 + bool
  144 + recompress_flate() const
  145 + {
  146 + return recompress_flate_;
  147 + }
  148 +
  149 + Config&
  150 + recompress_flate(bool val)
  151 + {
  152 + recompress_flate_ = val;
  153 + return *this;
  154 + }
  155 +
  156 + Config& stream_data(qpdf_stream_data_e val);
  157 +
  158 + std::string const&
  159 + forced_pdf_version() const
  160 + {
  161 + return forced_pdf_version_;
  162 + }
  163 +
  164 + Config&
  165 + forced_pdf_version(std::string const& val)
  166 + {
  167 + forced_pdf_version_ = val;
  168 + return *this;
  169 + }
  170 +
  171 + Config&
  172 + forced_pdf_version(std::string const& val, int ext)
  173 + {
  174 + forced_pdf_version_ = val;
  175 + forced_extension_level_ = ext;
  176 + return *this;
  177 + }
  178 +
  179 + int
  180 + forced_extension_level() const
  181 + {
  182 + return forced_extension_level_;
  183 + }
  184 +
  185 + Config&
  186 + forced_extension_level(int val)
  187 + {
  188 + forced_extension_level_ = val;
  189 + return *this;
  190 + }
  191 +
  192 + std::string const&
  193 + extra_header_text()
  194 + {
  195 + return extra_header_text_;
  196 + }
  197 +
  198 + Config& extra_header_text(std::string const& val);
  199 +
  200 + bool
  201 + preserve_unreferenced() const
  202 + {
  203 + return preserve_unreferenced_;
  204 + }
  205 +
  206 + Config&
  207 + preserve_unreferenced(bool val)
  208 + {
  209 + preserve_unreferenced_ = val;
  210 + return *this;
  211 + }
  212 +
  213 + bool
  214 + no_original_object_ids() const
  215 + {
  216 + return no_original_object_ids_;
  217 + }
  218 +
  219 + Config&
  220 + no_original_object_ids(bool val)
  221 + {
  222 + no_original_object_ids_ = val;
  223 + return *this;
  224 + }
  225 +
  226 + bool
  227 + qdf() const
  228 + {
  229 + return qdf_;
  230 + }
  231 +
  232 + Config& qdf(bool val);
  233 +
  234 + bool
  235 + normalize_content() const
  236 + {
  237 + return normalize_content_;
  238 + }
  239 +
  240 + Config&
  241 + normalize_content(bool val)
  242 + {
  243 + normalize_content_ = val;
  244 + normalize_content_set_ = true;
  245 + return *this;
  246 + }
  247 +
  248 + bool
  249 + deterministic_id() const
  250 + {
  251 + return deterministic_id_;
  252 + }
  253 +
  254 + Config&
  255 + deterministic_id(bool val)
  256 + {
  257 + deterministic_id_ = val;
  258 + return *this;
  259 + }
  260 +
  261 + bool
  262 + static_id() const
  263 + {
  264 + return static_id_;
  265 + }
  266 +
  267 + Config&
  268 + static_id(bool val)
  269 + {
  270 + static_id_ = val;
  271 + return *this;
  272 + }
  273 +
  274 + bool
  275 + pclm() const
  276 + {
  277 + return pclm_;
  278 + }
  279 +
  280 + Config& pclm(bool val);
  281 +
  282 + private:
  283 + void
  284 + usage(std::string const& msg) const
  285 + {
  286 + if (!permissive_) {
  287 + throw QPDFUsage(msg);
  288 + }
  289 + }
  290 +
  291 + std::string forced_pdf_version_;
  292 + std::string extra_header_text_;
  293 + // For linearization only
  294 + std::string linearize_pass1_;
  295 +
  296 + qpdf_object_stream_e object_streams_{qpdf_o_preserve};
  297 + qpdf_stream_decode_level_e decode_level_{qpdf_dl_generalized};
  298 +
  299 + int forced_extension_level_{0};
  300 +
  301 + bool normalize_content_set_{false};
  302 + bool normalize_content_{false};
  303 + bool compress_streams_{true};
  304 + bool compress_streams_set_{false};
  305 + bool decode_level_set_{false};
  306 + bool recompress_flate_{false};
  307 + bool qdf_{false};
  308 + bool preserve_unreferenced_{false};
  309 + bool newline_before_endstream_{false};
  310 + bool deterministic_id_{false};
  311 + bool static_id_{false};
  312 + bool no_original_object_ids_{false};
  313 + bool direct_stream_lengths_{true};
  314 + bool preserve_encryption_{true};
  315 + bool linearize_{false};
  316 + bool pclm_{false};
  317 + bool encrypt_use_aes_{false};
  318 +
  319 + bool permissive_{true};
  320 + }; // class Writer::Config
  321 +
  322 + Writer() = delete;
  323 + Writer(Writer const&) = delete;
  324 + Writer(Writer&&) = delete;
  325 + Writer& operator=(Writer const&) = delete;
  326 + Writer& operator=(Writer&&) = delete;
  327 + ~Writer() = default;
  328 +
  329 + Writer(QPDF& qpdf, Config cfg);
  330 +
  331 + }; // class Writer
  332 +} // namespace qpdf
  333 +
12 334 struct QPDFWriter::Object
13 335 {
14 336 int renumber{0};
... ... @@ -24,7 +346,7 @@ struct QPDFWriter::NewObject
24 346  
25 347 class QPDFWriter::ObjTable: public ::ObjTable<QPDFWriter::Object>
26 348 {
27   - friend class QPDFWriter;
  349 + friend class qpdf::impl::Writer;
28 350  
29 351 public:
30 352 bool
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -22,6 +22,11 @@ namespace qpdf
22 22 class OffsetBuffer;
23 23 } // namespace is
24 24  
  25 + namespace impl
  26 + {
  27 + using Doc = QPDF::Doc;
  28 + }
  29 +
25 30 class Doc: public QPDF
26 31 {
27 32 public:
... ... @@ -271,7 +276,6 @@ class QPDF::Doc
271 276 class Pages;
272 277 class ParseGuard;
273 278 class Resolver;
274   - class Writer;
275 279  
276 280 // This is the common base-class for all document components. It is used by the other document
277 281 // components to access common functionality. It is not meant to be used directly by the user.
... ... @@ -286,6 +290,7 @@ class QPDF::Doc
286 290 ~Common() = default;
287 291  
288 292 inline Common(QPDF& qpdf, QPDF::Members* m);
  293 + inline Common(Doc& doc);
289 294  
290 295 void stopOnError(std::string const& message);
291 296 void warn(QPDFExc const& e);
... ... @@ -546,7 +551,7 @@ class QPDF::Doc::Linearization: Common
546 551 ~Linearization() = default;
547 552  
548 553 Linearization(Doc& doc) :
549   - Common(doc.qpdf, doc.m)
  554 + Common(doc)
550 555 {
551 556 }
552 557  
... ... @@ -567,7 +572,7 @@ class QPDF::Doc::Linearization: Common
567 572  
568 573 // Get lists of all objects in order according to the part of a linearized file that they
569 574 // belong to.
570   - void getLinearizedParts(
  575 + void parts(
571 576 QPDFWriter::ObjTable const& obj,
572 577 std::vector<QPDFObjectHandle>& part4,
573 578 std::vector<QPDFObjectHandle>& part6,
... ... @@ -940,7 +945,7 @@ class QPDF::Doc::Objects: Common
940 945 ~Objects() = default;
941 946  
942 947 Objects(Doc& doc) :
943   - Common(doc.qpdf, doc.m),
  948 + Common(doc),
944 949 foreign_(*this),
945 950 streams_(*this)
946 951 {
... ... @@ -987,18 +992,19 @@ class QPDF::Doc::Objects: Common
987 992 QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
988 993 std::shared_ptr<QPDFObject> getObjectForParser(int id, int gen, bool parse_pdf);
989 994 std::shared_ptr<QPDFObject> getObjectForJSON(int id, int gen);
990   - size_t tableSize();
  995 + size_t table_size();
991 996  
992 997 // For QPDFWriter:
993 998  
994   - std::map<QPDFObjGen, QPDFXRefEntry> const& getXRefTableInternal();
  999 + std::map<QPDFObjGen, QPDFXRefEntry> const& xref_table();
  1000 + std::vector<QPDFObjGen> compressible_vector();
  1001 + std::vector<bool> compressible_set();
  1002 +
  1003 + private:
995 1004 // Get a list of objects that would be permitted in an object stream.
996 1005 template <typename T>
997   - std::vector<T> getCompressibleObjGens();
998   - std::vector<QPDFObjGen> getCompressibleObjVector();
999   - std::vector<bool> getCompressibleObjSet();
  1006 + std::vector<T> compressible();
1000 1007  
1001   - private:
1002 1008 void setTrailer(QPDFObjectHandle obj);
1003 1009 void reconstruct_xref(QPDFExc& e, bool found_startxref = true);
1004 1010 void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false);
... ... @@ -1060,7 +1066,7 @@ class QPDF::Doc::Pages: Common
1060 1066 ~Pages() = default;
1061 1067  
1062 1068 Pages(Doc& doc) :
1063   - Common(doc.qpdf, doc.m)
  1069 + Common(doc)
1064 1070 {
1065 1071 }
1066 1072  
... ... @@ -1214,6 +1220,11 @@ inline QPDF::Doc::Common::Common(QPDF&amp; qpdf, QPDF::Members* m) :
1214 1220 {
1215 1221 }
1216 1222  
  1223 +inline QPDF::Doc::Common::Common(Doc& doc) :
  1224 + Common(doc.qpdf, doc.m)
  1225 +{
  1226 +}
  1227 +
1217 1228 inline QPDF::Doc::Linearization&
1218 1229 QPDF::Doc::linearization()
1219 1230 {
... ... @@ -1245,7 +1256,7 @@ QPDF::doc()
1245 1256 }
1246 1257  
1247 1258 inline QPDF::Doc::Objects::Foreign::Copier::Copier(QPDF& qpdf) :
1248   - Common(qpdf, qpdf.doc().m)
  1259 + Common(qpdf.doc())
1249 1260 {
1250 1261 }
1251 1262  
... ...
qpdf/qpdf.testcov
... ... @@ -46,7 +46,6 @@ QPDFObjectHandle copy stream 1
46 46 QPDF ignoring XRefStm in trailer 0
47 47 SF_FlateLzwDecode PNG filter 0
48 48 QPDF xref /Index is array 1
49   -QPDFWriter encrypt object stream 0
50 49 QPDF exclude indirect length 0
51 50 QPDF exclude encryption dictionary 0
52 51 QPDF_Stream pipeStreamData with null pipeline 0
... ...