Commit 34f013c1be56abac1104812938eb0af568df99e1

Authored by Jay Berkenbilt
1 parent 20a13482

Allow --file and --range with --pages

Accept --file and --range as named parameters in additional to
allowing positional arguments. This is in preparation for adding
additional flags.
examples/qpdf-job.cc
... ... @@ -40,7 +40,9 @@ main(int argc, char* argv[])
40 40 ->inputFile("in.pdf")
41 41 ->outputFile("out1.pdf")
42 42 ->pages()
43   - ->pageSpec(".", "1")
  43 + // Prior to qpdf 11.9.0, call ->pageSpec(file, range, password)
  44 + ->file(".")
  45 + ->range("1")
44 46 ->endPages()
45 47 ->linearize()
46 48 ->staticId() // for testing only
... ...
include/qpdf/QPDFJob.hh
... ... @@ -243,6 +243,8 @@ class QPDFJob
243 243 public:
244 244 QPDF_DLL
245 245 Config* endPages();
  246 + // From qpdf 11.9.0, you can call file(), range(), and password(). Each call to file()
  247 + // starts a new page spec.
246 248 QPDF_DLL
247 249 PagesConfig* pageSpec(
248 250 std::string const& filename, std::string const& range, char const* password = nullptr);
... ...
include/qpdf/auto_job_c_pages.hh
... ... @@ -5,3 +5,6 @@
5 5 //
6 6 // clang-format off
7 7 //
  8 +QPDF_DLL PagesConfig* file(std::string const& parameter);
  9 +QPDF_DLL PagesConfig* range(std::string const& parameter);
  10 +QPDF_DLL PagesConfig* password(std::string const& parameter);
... ...
job.sums
... ... @@ -5,16 +5,16 @@ include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf2
5 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
7 7 include/qpdf/auto_job_c_main.hh dbfc221d1533120d1aa9c361d8d2483dea5fcb1c0fd95144d98d305e64ed32a6
8   -include/qpdf/auto_job_c_pages.hh b3cc0f21029f6d89efa043dcdbfa183cb59325b6506001c18911614fe8e568ec
  8 +include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506
9 9 include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1
10   -job.yml 8ad309ac41520b34692bcf22fd5c2ef4810ff69562aed606bd57df7bf589bc43
11   -libqpdf/qpdf/auto_job_decl.hh 1e8d73891bd1f0b5df5a5ca7405fb76d2d0fd024941b8c1b86489f1b5f9c5772
12   -libqpdf/qpdf/auto_job_help.hh 8c172913920a5273e04dc4d2059f2d78fc475960ac1738271357056beb02dd27
13   -libqpdf/qpdf/auto_job_init.hh ea272fd6a6a5e4d23cabd70a7b7d5ecc543b6304008c656dcba2d353d378efc2
14   -libqpdf/qpdf/auto_job_json_decl.hh 10ffb0d0e5ca09809a5d5d78f66dee393dfd2653a23441436465fd5ace151880
15   -libqpdf/qpdf/auto_job_json_init.hh 9c3839877ab3b15a47e92086f0b5616da33fd4970538cc423d3b0a7ff33ce66a
16   -libqpdf/qpdf/auto_job_schema.hh a882939b202d48ad1c0751c094f671ad7aad0fc04c3a4446ad83675db365c8a2
  10 +job.yml 45761edeca048c7aa3e99340fcda1b6cd8efe4cc4c8b8a6628580243a4f49b57
  11 +libqpdf/qpdf/auto_job_decl.hh 20d6affe1e260f5a1af4f1d82a820b933835440ff03020e877382da2e8dac6c6
  12 +libqpdf/qpdf/auto_job_help.hh b19f8a7433c70df70b42f893a8964c801aa2bb78eecaa13cffab7add2ff81e0a
  13 +libqpdf/qpdf/auto_job_init.hh d74759d4999201a89dafddf6f0c855e9151bbf77ea91a92d6806510292950123
  14 +libqpdf/qpdf/auto_job_json_decl.hh 485540cde820987cfbed0aa7642a6416f2bd37164c8d4f2322f1381e73edf903
  15 +libqpdf/qpdf/auto_job_json_init.hh c8de8658daa82115b49bf084cebe1be0b8aea73f864a219d7349acc0982b56fe
  16 +libqpdf/qpdf/auto_job_schema.hh 3e000b87255bee62ba29b794d67b2ae97cbbdfdb78be3878c51786913564901e
17 17 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
18   -manual/cli.rst 43923fab0def5f76e537f03ba2f650270a1ae858a15747e355db2a6ea396f1a0
19   -manual/qpdf.1 cd335812d450ca83be3c7fe165299d3454b26b4999295f671d57e6b24f6ea7a1
  18 +manual/cli.rst 408e17dc13d37befe34badc400dd34d3c283952d17ee3bf9a9d44898af3dabc7
  19 +manual/qpdf.1 c99d66833aee7a2294176875ca2e9ddf2531d4ab8fb282ea5c45cb82a5d028ea
20 20 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
... ...
... ... @@ -195,9 +195,9 @@ options:
195 195 config: c_pages
196 196 prefix: Pages
197 197 positional: true
198   - manual:
199   - - password
200 198 required_parameter:
  199 + file: file
  200 + range: page-range
201 201 password: password
202 202 - table: encryption
203 203 config: c_main
... ... @@ -436,9 +436,9 @@ json:
436 436 oi-min-width:
437 437 optimize-images:
438 438 pages:
439   - - _file: "source for for pages"
  439 + - file:
440 440 Pages.password:
441   - _range: "page range"
  441 + range:
442 442 remove-page-labels:
443 443 report-memory-usage:
444 444 rotate:
... ...
libqpdf/QPDFJob.cc
... ... @@ -2342,6 +2342,9 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea
2342 2342 if (page_spec.filename == ".") {
2343 2343 page_spec.filename = m->infilename.get();
2344 2344 }
  2345 + if (page_spec.range.empty()) {
  2346 + page_spec.range = "1-z";
  2347 + }
2345 2348 }
2346 2349  
2347 2350 if (!m->keep_files_open_set) {
... ...
libqpdf/QPDFJob_argv.cc
... ... @@ -34,9 +34,10 @@ namespace
34 34 std::shared_ptr<QPDFJob::UOConfig> c_uo;
35 35 std::shared_ptr<QPDFJob::EncConfig> c_enc;
36 36 std::vector<std::string> accumulated_args;
37   - std::shared_ptr<char> pages_password{nullptr};
38 37 std::string user_password;
39 38 std::string owner_password;
  39 + bool called_pages_file{false};
  40 + bool called_pages_range{false};
40 41 bool used_enc_password_args{false};
41 42 bool gave_input{false};
42 43 bool gave_output{false};
... ... @@ -237,81 +238,43 @@ ArgParser::argPages()
237 238 }
238 239  
239 240 void
240   -ArgParser::argPagesPassword(std::string const& parameter)
241   -{
242   - if (this->pages_password) {
243   - QTC::TC("qpdf", "QPDFJob duplicated pages password");
244   - usage("--password already specified for this file");
245   - }
246   - if (this->accumulated_args.size() != 1) {
247   - QTC::TC("qpdf", "QPDFJob misplaced pages password");
248   - usage("in --pages, --password must immediately follow a file name");
249   - }
250   - this->pages_password = QUtil::make_shared_cstr(parameter);
251   -}
252   -
253   -void
254 241 ArgParser::argPagesPositional(std::string const& arg)
255 242 {
256   - if (arg.empty()) {
257   - if (this->accumulated_args.empty()) {
258   - return;
259   - }
260   - } else {
261   - this->accumulated_args.push_back(arg);
  243 + if (!called_pages_file) {
  244 + c_pages->file(arg);
  245 + called_pages_file = true;
  246 + return;
262 247 }
263   -
264   - std::string file = this->accumulated_args.at(0);
265   - char const* range_p = nullptr;
266   -
267   - size_t n_args = this->accumulated_args.size();
268   - if (n_args >= 2) {
269   - // will be copied before accumulated_args is cleared
270   - range_p = this->accumulated_args.at(1).c_str();
  248 + if (called_pages_range) {
  249 + c_pages->file(arg);
  250 + called_pages_range = false;
  251 + return;
271 252 }
272   -
273   - // See if the user omitted the range entirely, in which case we assume "1-z".
274   - std::string next_file;
275   - if (range_p == nullptr) {
276   - if (arg.empty()) {
277   - // The filename or password was the last argument
278   - QTC::TC("qpdf", "QPDFJob pages range omitted at end", this->pages_password ? 0 : 1);
  253 + // This could be a range or a file. Try parsing.
  254 + try {
  255 + QUtil::parse_numrange(arg.c_str(), 0);
  256 + c_pages->range(arg);
  257 + called_pages_range = true;
  258 + } catch (std::runtime_error& e1) {
  259 + // The range is invalid. Let's see if it's a file.
  260 + if (arg == ".") {
  261 + // "." means the input file.
  262 + QTC::TC("qpdf", "QPDFJob pages range omitted with .");
  263 + } else if (QUtil::file_can_be_opened(arg.c_str())) {
  264 + QTC::TC("qpdf", "QPDFJob pages range omitted in middle");
  265 + // Yup, it's a file.
279 266 } else {
280   - // We need to accumulate some more arguments
281   - return;
  267 + // Give the range error
  268 + usage(e1.what());
282 269 }
283   - } else {
284   - try {
285   - QUtil::parse_numrange(range_p, 0);
286   - } catch (std::runtime_error& e1) {
287   - // The range is invalid. Let's see if it's a file.
288   - if (strcmp(range_p, ".") == 0) {
289   - // "." means the input file.
290   - QTC::TC("qpdf", "QPDFJob pages range omitted with .");
291   - } else if (QUtil::file_can_be_opened(range_p)) {
292   - QTC::TC("qpdf", "QPDFJob pages range omitted in middle");
293   - // Yup, it's a file.
294   - } else {
295   - // Give the range error
296   - usage(e1.what());
297   - }
298   - next_file = range_p;
299   - range_p = nullptr;
300   - }
301   - }
302   - std::string range(range_p ? range_p : "1-z");
303   - this->c_pages->pageSpec(file, range, this->pages_password.get());
304   - this->accumulated_args.clear();
305   - this->pages_password = nullptr;
306   - if (!next_file.empty()) {
307   - this->accumulated_args.push_back(next_file);
  270 + c_pages->file(arg);
  271 + called_pages_range = false;
308 272 }
309 273 }
310 274  
311 275 void
312 276 ArgParser::argEndPages()
313 277 {
314   - argPagesPositional("");
315 278 c_pages->endPages();
316 279 c_pages = nullptr;
317 280 }
... ...
libqpdf/QPDFJob_config.cc
... ... @@ -968,6 +968,45 @@ QPDFJob::PagesConfig::pageSpec(
968 968 return this;
969 969 }
970 970  
  971 +QPDFJob::PagesConfig*
  972 +QPDFJob::PagesConfig::file(std::string const& arg)
  973 +{
  974 + this->config->o.m->page_specs.emplace_back(arg, nullptr, "");
  975 + return this;
  976 +}
  977 +
  978 +QPDFJob::PagesConfig*
  979 +QPDFJob::PagesConfig::range(std::string const& arg)
  980 +{
  981 + if (config->o.m->page_specs.empty()) {
  982 + QTC::TC("qpdf", "QPDFJob misplaced page range");
  983 + usage("in --range must follow a file name");
  984 + }
  985 + auto& last = config->o.m->page_specs.back();
  986 + if (!last.range.empty()) {
  987 + QTC::TC("qpdf", "QPDFJob duplicated range");
  988 + usage("--range already specified for this file");
  989 + }
  990 + last.range = arg;
  991 + return this;
  992 +}
  993 +
  994 +QPDFJob::PagesConfig*
  995 +QPDFJob::PagesConfig::password(std::string const& arg)
  996 +{
  997 + if (config->o.m->page_specs.empty()) {
  998 + QTC::TC("qpdf", "QPDFJob misplaced pages password");
  999 + usage("in --pages, --password must follow a file name");
  1000 + }
  1001 + auto& last = config->o.m->page_specs.back();
  1002 + if (last.password) {
  1003 + QTC::TC("qpdf", "QPDFJob duplicated pages password");
  1004 + usage("--password already specified for this file");
  1005 + }
  1006 + last.password = QUtil::make_shared_cstr(arg);
  1007 + return this;
  1008 +}
  1009 +
971 1010 std::shared_ptr<QPDFJob::UOConfig>
972 1011 QPDFJob::Config::overlay()
973 1012 {
... ...
libqpdf/QPDFJob_json.cc
... ... @@ -467,25 +467,17 @@ Handlers::endPagesArray()
467 467 void
468 468 Handlers::beginPages(JSON j)
469 469 {
470   - std::string file;
471   - std::string range("1-z");
472   - std::string password;
473 470 bool file_seen = false;
474   - bool password_seen = false;
475   - j.forEachDictItem([&](std::string const& key, JSON value) {
  471 + j.forEachDictItem([&](std::string const& key, JSON const& value) {
476 472 if (key == "file") {
477   - file_seen = value.getString(file);
478   - } else if (key == "range") {
479   - value.getString(range);
480   - } else if (key == "password") {
481   - password_seen = value.getString(password);
  473 + std::string v;
  474 + file_seen = value.getString(v);
482 475 }
483 476 });
484 477 if (!file_seen) {
485 478 QTC::TC("qpdf", "QPDFJob json pages no file");
486 479 usage("file is required in page specification");
487 480 }
488   - this->c_pages->pageSpec(file, range, password_seen ? password.c_str() : nullptr);
489 481 }
490 482  
491 483 void
... ... @@ -495,24 +487,9 @@ Handlers::endPages()
495 487 }
496 488  
497 489 void
498   -Handlers::setupPagesFile()
499   -{
500   - // handled in beginPages
501   - ignoreItem();
502   -}
503   -
504   -void
505 490 Handlers::setupPagesPassword()
506 491 {
507   - // handled in beginPages
508   - ignoreItem();
509   -}
510   -
511   -void
512   -Handlers::setupPagesRange()
513   -{
514   - // handled in beginPages
515   - ignoreItem();
  492 + addParameter([this](char const* p) { c_pages->password(p); });
516 493 }
517 494  
518 495 void
... ...
libqpdf/qpdf/auto_job_decl.hh
... ... @@ -31,7 +31,6 @@ void argReplaceInput();
31 31 void argSetPageLabels();
32 32 void argUnderlay();
33 33 void argPagesPositional(std::string const&);
34   -void argPagesPassword(std::string const&);
35 34 void argEndPages();
36 35 void argEncPositional(std::string const&);
37 36 void argEncUserPassword(std::string const&);
... ...
libqpdf/qpdf/auto_job_help.hh
... ... @@ -311,10 +311,24 @@ static void add_help_4(QPDFArgParser&amp; ap)
311 311 ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of
312 312 the PDF, causing the PDF to render differently from the original.
313 313 )");
314   -ap.addOptionHelp("--pages", "modification", "begin page selection", R"(--pages file [--password=password] [page-range] [...] --
  314 +ap.addOptionHelp("--pages", "modification", "begin page selection", R"(--pages [--file=]file [options] [...] --
315 315  
316 316 Run qpdf --help=page-selection for details.
317 317 )");
  318 +ap.addOptionHelp("--file", "modification", "source for pages", R"(--file=file
  319 +
  320 +Specify the file for the current page operation. This is used
  321 +with --pages, --overlay, and --underlay and appears between the
  322 +option and the terminating --. Run qpdf --help=page-selection
  323 +for details.
  324 +)");
  325 +ap.addOptionHelp("--range", "modification", "page range", R"(--range=numeric-range
  326 +
  327 +Specify the page range for the current page operation with
  328 +--pages. If omitted, all pages are selected. This is used
  329 +with --pages and appears between --pages and --. Run
  330 +qpdf --help=page-selection for details.
  331 +)");
318 332 ap.addOptionHelp("--collate", "modification", "collate with --pages", R"(--collate[=n[,m,...]]
319 333  
320 334 Collate rather than concatenate pages specified with --pages.
... ... @@ -437,6 +451,9 @@ iv, then the remaining pages with Arabic numerals starting with
437 451 1 and continuing sequentially until the end of the document. For
438 452 additional examples, please consult the manual.
439 453 )");
  454 +}
  455 +static void add_help_5(QPDFArgParser& ap)
  456 +{
440 457 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage:
441 458  
442 459 --encrypt \
... ... @@ -520,9 +537,6 @@ ap.addOptionHelp(&quot;--user-password&quot;, &quot;encryption&quot;, &quot;specify user password&quot;, R&quot;(--
520 537  
521 538 Set the user password of the encrypted file.
522 539 )");
523   -}
524   -static void add_help_5(QPDFArgParser& ap)
525   -{
526 540 ap.addOptionHelp("--owner-password", "encryption", "specify owner password", R"(--owner-password=owner-password
527 541  
528 542 Set the owner password of the encrypted file.
... ... @@ -620,12 +634,24 @@ should not be used except for compatibility testing.
620 634 )");
621 635 ap.addHelpTopic("page-selection", "select pages from one or more files", R"(Use the --pages option to select pages from multiple files. Usage:
622 636  
  637 +qpdf in.pdf --pages --file=input-file \
  638 + [--range=page-range] [--password=password] [...] -- out.pdf
  639 +
  640 +OR
  641 +
623 642 qpdf in.pdf --pages input-file [--password=password] [page-range] \
624 643 [...] -- out.pdf
625 644  
626 645 Between --pages and the -- that terminates pages option, repeat
627 646 the following:
628 647  
  648 +--file=filename [--range=page-range] [--password=password] [options]
  649 +
  650 +For compatibility, the file and range can be specified
  651 +positionally. qpdf versions prior to 11.9.0
  652 +require --password=password to immediately follow the filename. In
  653 +the older syntax, repeat the following:
  654 +
629 655 filename [--password=password] [page-range]
630 656  
631 657 Document-level information, such as outlines, tags, etc., is taken
... ... @@ -654,7 +680,7 @@ Examples:
654 680 information from in.pdf is retained. Note the use of "." to refer
655 681 to in.pdf.
656 682  
657   - qpdf in.pdf --pages . a.pdf b.pdf:even -- out.pdf
  683 + qpdf in.pdf --pages . a.pdf b.pdf 1-z:even -- out.pdf
658 684  
659 685 - Take all the pages from a.pdf, all the pages from b.pdf in
660 686 reverse, and only pages 3 and 6 from c.pdf and write the result
... ... @@ -687,6 +713,9 @@ those pages to be repeated after the original pages are exhausted.
687 713  
688 714 Run qpdf --help=page-ranges for help with page ranges.
689 715 )");
  716 +}
  717 +static void add_help_6(QPDFArgParser& ap)
  718 +{
690 719 ap.addOptionHelp("--to", "overlay-underlay", "destination pages for underlay/overlay", R"(--to=page-range
691 720  
692 721 Specify the range of pages in the primary output to apply
... ... @@ -700,9 +729,6 @@ the destination pages. See qpdf --help=page-ranges for help
700 729 with the page range syntax. The page range may be omitted
701 730 if --repeat is used.
702 731 )");
703   -}
704   -static void add_help_6(QPDFArgParser& ap)
705   -{
706 732 ap.addOptionHelp("--repeat", "overlay-underlay", "overlay/underlay pages to repeat", R"(--repeat=page-range
707 733  
708 734 Specify pages from the overlay/underlay that are repeated after
... ... @@ -801,6 +827,9 @@ ap.addHelpTopic(&quot;inspection&quot;, &quot;inspect PDF files&quot;, R&quot;(These options provide tool
801 827 the options in this section are specified, no output file may be
802 828 given.
803 829 )");
  830 +}
  831 +static void add_help_7(QPDFArgParser& ap)
  832 +{
804 833 ap.addOptionHelp("--is-encrypted", "inspection", "silently test whether a file is encrypted", R"(Silently exit with a code indicating the file's encryption status:
805 834  
806 835 0: the file is encrypted
... ... @@ -817,9 +846,6 @@ ap.addOptionHelp(&quot;--requires-password&quot;, &quot;inspection&quot;, &quot;silently test a file&#39;s pa
817 846 2: the file is not encrypted
818 847 3: the file is encrypted, and correct password (if any) has been supplied
819 848 )");
820   -}
821   -static void add_help_7(QPDFArgParser& ap)
822   -{
823 849 ap.addOptionHelp("--check", "inspection", "partially check whether PDF is valid", R"(Check the structure of the PDF file as well as a number of other
824 850 aspects of the file, and write information about the file to
825 851 standard output. Note that qpdf does not perform any validation
... ... @@ -894,6 +920,9 @@ Describe the format of the JSON output by writing to standard
894 920 output a JSON object with the same keys and with values
895 921 containing descriptive text.
896 922 )");
  923 +}
  924 +static void add_help_8(QPDFArgParser& ap)
  925 +{
897 926 ap.addOptionHelp("--json-key", "json", "limit which keys are in JSON output", R"(--json-key=key
898 927  
899 928 This option is repeatable. If given, only the specified
... ... @@ -907,9 +936,6 @@ This option is repeatable. If given, only specified objects will
907 936 be shown in the "objects" key of the JSON output. Otherwise, all
908 937 objects will be shown.
909 938 )");
910   -}
911   -static void add_help_8(QPDFArgParser& ap)
912   -{
913 939 ap.addOptionHelp("--json-stream-data", "json", "how to handle streams in json output", R"(--json-stream-data={none|inline|file}
914 940  
915 941 When used with --json, this option controls whether streams in
... ...
libqpdf/qpdf/auto_job_init.hh
... ... @@ -127,7 +127,9 @@ this-&gt;ap.addChoices(&quot;json&quot;, [this](std::string const&amp; x){c_main-&gt;json(x);}, fals
127 127 this->ap.addChoices("json-output", [this](std::string const& x){c_main->jsonOutput(x);}, false, json_output_choices);
128 128 this->ap.registerOptionTable("pages", b(&ArgParser::argEndPages));
129 129 this->ap.addPositional(p(&ArgParser::argPagesPositional));
130   -this->ap.addRequiredParameter("password", p(&ArgParser::argPagesPassword), "password");
  130 +this->ap.addRequiredParameter("file", [this](std::string const& x){c_pages->file(x);}, "file");
  131 +this->ap.addRequiredParameter("range", [this](std::string const& x){c_pages->range(x);}, "page-range");
  132 +this->ap.addRequiredParameter("password", [this](std::string const& x){c_pages->password(x);}, "password");
131 133 this->ap.registerOptionTable("encryption", b(&ArgParser::argEndEncryption));
132 134 this->ap.addPositional(p(&ArgParser::argEncPositional));
133 135 this->ap.addRequiredParameter("user-password", p(&ArgParser::argEncUserPassword), "user_password");
... ...
libqpdf/qpdf/auto_job_json_decl.hh
... ... @@ -41,9 +41,7 @@ void beginPagesArray(JSON);
41 41 void endPagesArray();
42 42 void beginPages(JSON);
43 43 void endPages();
44   -void setupPagesFile();
45 44 void setupPagesPassword();
46   -void setupPagesRange();
47 45 void beginSetPageLabelsArray(JSON);
48 46 void endSetPageLabelsArray();
49 47 void setupSetPageLabels();
... ...
libqpdf/qpdf/auto_job_json_init.hh
... ... @@ -402,13 +402,13 @@ pushKey(&quot;pages&quot;);
402 402 beginArray(bindJSON(&Handlers::beginPagesArray), bindBare(&Handlers::endPagesArray)); // .pages[]
403 403 beginDict(bindJSON(&Handlers::beginPages), bindBare(&Handlers::endPages)); // .pages
404 404 pushKey("file");
405   -setupPagesFile();
  405 +addParameter([this](std::string const& p) { c_pages->file(p); });
406 406 popHandler(); // key: file
407 407 pushKey("password");
408 408 setupPagesPassword();
409 409 popHandler(); // key: password
410 410 pushKey("range");
411   -setupPagesRange();
  411 +addParameter([this](std::string const& p) { c_pages->range(p); });
412 412 popHandler(); // key: range
413 413 popHandler(); // array: .pages[]
414 414 popHandler(); // key: pages
... ...
libqpdf/qpdf/auto_job_schema.hh
... ... @@ -140,7 +140,7 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({
140 140 "optimizeImages": "use efficient compression for images",
141 141 "pages": [
142 142 {
143   - "file": "source for for pages",
  143 + "file": "source for pages",
144 144 "password": "password for encrypted file",
145 145 "range": "page range"
146 146 }
... ...
manual/cli.rst
... ... @@ -1388,7 +1388,7 @@ PDF, causing the PDF to render differently from the original. See also
1388 1388 Related Options
1389 1389 ~~~~~~~~~~~~~~~
1390 1390  
1391   -.. qpdf:option:: --pages file [--password=password] [page-range] [...] --
  1391 +.. qpdf:option:: --pages [--file=]file [options] [...] --
1392 1392  
1393 1393 .. help: begin page selection
1394 1394  
... ... @@ -1403,6 +1403,38 @@ Related Options
1403 1403 See also :qpdf:ref:`--split-pages`, :qpdf:ref:`--collate`,
1404 1404 :ref:`page-ranges`.
1405 1405  
  1406 +.. qpdf:option:: --file=file
  1407 +
  1408 + .. help: source for pages
  1409 +
  1410 + Specify the file for the current page operation. This is used
  1411 + with --pages, --overlay, and --underlay and appears between the
  1412 + option and the terminating --. Run qpdf --help=page-selection
  1413 + for details.
  1414 +
  1415 + Specify the file for the current page operation. This option is
  1416 + used with :qpdf:ref:`--pages`, :qpdf:ref:`--overlay` and
  1417 + :qpdf:ref:`--underlay` and appears between the option and the
  1418 + terminating ``--``.
  1419 +
  1420 + Please see :ref:`page-selection` for additional details.
  1421 +
  1422 +.. qpdf:option:: --range=numeric-range
  1423 +
  1424 + .. help: page range
  1425 +
  1426 + Specify the page range for the current page operation with
  1427 + --pages. If omitted, all pages are selected. This is used
  1428 + with --pages and appears between --pages and --. Run
  1429 + qpdf --help=page-selection for details.
  1430 +
  1431 + Specify the page range for the current page operation with
  1432 + :qpdf:ref:`--pages`. If omitted, all pages are selected. This
  1433 + option is used with :qpdf:ref:`--pages` and appears between
  1434 + :qpdf:ref:`--pages` and ``--``.
  1435 +
  1436 + Please see :ref:`page-selection` for additional details.
  1437 +
1406 1438 .. qpdf:option:: --collate[=n[,m,...]]
1407 1439  
1408 1440 .. help: collate with --pages
... ... @@ -2424,12 +2456,24 @@ Page Selection
2424 2456  
2425 2457 Use the --pages option to select pages from multiple files. Usage:
2426 2458  
  2459 + qpdf in.pdf --pages --file=input-file \
  2460 + [--range=page-range] [--password=password] [...] -- out.pdf
  2461 +
  2462 + OR
  2463 +
2427 2464 qpdf in.pdf --pages input-file [--password=password] [page-range] \
2428 2465 [...] -- out.pdf
2429 2466  
2430 2467 Between --pages and the -- that terminates pages option, repeat
2431 2468 the following:
2432 2469  
  2470 + --file=filename [--range=page-range] [--password=password] [options]
  2471 +
  2472 + For compatibility, the file and range can be specified
  2473 + positionally. qpdf versions prior to 11.9.0
  2474 + require --password=password to immediately follow the filename. In
  2475 + the older syntax, repeat the following:
  2476 +
2433 2477 filename [--password=password] [page-range]
2434 2478  
2435 2479 Document-level information, such as outlines, tags, etc., is taken
... ... @@ -2458,7 +2502,7 @@ Page Selection
2458 2502 information from in.pdf is retained. Note the use of "." to refer
2459 2503 to in.pdf.
2460 2504  
2461   - qpdf in.pdf --pages . a.pdf b.pdf:even -- out.pdf
  2505 + qpdf in.pdf --pages . a.pdf b.pdf 1-z:even -- out.pdf
2462 2506  
2463 2507 - Take all the pages from a.pdf, all the pages from b.pdf in
2464 2508 reverse, and only pages 3 and 6 from c.pdf and write the result
... ... @@ -2472,14 +2516,29 @@ Page Selection
2472 2516 split and merge PDF files by selecting pages from one or more input
2473 2517 files.
2474 2518  
2475   -Usage: :samp:`qpdf {in.pdf} --pages input-file [--password={password}] [{page-range}] [...] -- {out.pdf}`
  2519 +::
2476 2520  
2477   -Between ``--pages`` and the ``--`` that terminates pages option,
2478   -repeat the following:
  2521 + qpdf primary-input.pdf \
  2522 + --file=input.pdf \
  2523 + [--range=page-range] \
  2524 + [--password=password] \
  2525 + [...] \
  2526 + -- output.pdf
2479 2527  
2480   -:samp:`{filename} [--password={password}] [{page-range}]`
  2528 +OR
  2529 +
  2530 +::
  2531 +
  2532 + qpdf primary-input.pdf \
  2533 + input.pdf [--password=password] [page-range] \
  2534 + [...] -- output.pdf
2481 2535  
2482 2536 Notes:
  2537 + - The first form, with :qpdf:ref:`--file` and :qpdf:ref:`--range`,
  2538 + was introduced in qpdf 11.9.0. In this form, the
  2539 + :qpdf:ref:`--range` and :qpdf:ref:`--password` options apply to
  2540 + the most recently specified :qpdf:ref:`--file` option.
  2541 +
2483 2542 - The password option is needed only for password-protected files.
2484 2543 If you specify the same file more than once, you only need to supply
2485 2544 the password the first time.
... ... @@ -2518,8 +2577,7 @@ Examples
2518 2577  
2519 2578 ::
2520 2579  
2521   - qpdf in.pdf --pages . a.pdf b.pdf:even -- out.pdf
2522   -
  2580 + qpdf in.pdf --pages . a.pdf b.pdf 1-z:even -- out.pdf
2523 2581  
2524 2582 - Take all the pages from :file:`a.pdf`, all the pages from
2525 2583 :file:`b.pdf` in reverse, and only pages 3 and 6 from :file:`c.pdf`
... ... @@ -2529,7 +2587,9 @@ Examples
2529 2587  
2530 2588 ::
2531 2589  
2532   - qpdf --empty --pages a.pdf b.pdf --password=x z-1 c.pdf 3,6
  2590 + qpdf --empty --pages --file=a.pdf \
  2591 + --file=b.pdf --password=x --range=z-1 \
  2592 + --file=c.pdf --range=3,6 -- out.pdf
2533 2593  
2534 2594 - Scan a document with double-sided printing by scanning the fronts
2535 2595 into :file:`odd.pdf` and the backs into :file:`even.pdf`. Collate
... ... @@ -2542,6 +2602,8 @@ Examples
2542 2602 qpdf --collate odd.pdf --pages . even.pdf -- all.pdf
2543 2603 OR
2544 2604 qpdf --collate --empty --pages odd.pdf even.pdf -- all.pdf
  2605 + OR
  2606 + qpdf --collate --empty --pages --file=odd.pdf --file=even.pdf -- all.pdf
2545 2607  
2546 2608 - When collating, any number of files and page ranges can be
2547 2609 specified. If any file has fewer pages, that file is just skipped
... ...
manual/qpdf.1
... ... @@ -409,10 +409,26 @@ the PDF, causing the PDF to render differently from the original.
409 409 Related Options:
410 410 .TP
411 411 .B --pages \-\- begin page selection
412   ---pages file [--password=password] [page-range] [...] --
  412 +--pages [--file=]file [options] [...] --
413 413  
414 414 Run qpdf --help=page-selection for details.
415 415 .TP
  416 +.B --file \-\- source for pages
  417 +--file=file
  418 +
  419 +Specify the file for the current page operation. This is used
  420 +with --pages, --overlay, and --underlay and appears between the
  421 +option and the terminating --. Run qpdf --help=page-selection
  422 +for details.
  423 +.TP
  424 +.B --range \-\- page range
  425 +--range=numeric-range
  426 +
  427 +Specify the page range for the current page operation with
  428 +--pages. If omitted, all pages are selected. This is used
  429 +with --pages and appears between --pages and --. Run
  430 +qpdf --help=page-selection for details.
  431 +.TP
416 432 .B --collate \-\- collate with --pages
417 433 --collate[=n[,m,...]]
418 434  
... ... @@ -754,12 +770,24 @@ should not be used except for compatibility testing.
754 770 .SH PAGE-SELECTION (select pages from one or more files)
755 771 Use the --pages option to select pages from multiple files. Usage:
756 772  
  773 +qpdf in.pdf --pages --file=input-file \
  774 + [--range=page-range] [--password=password] [...] -- out.pdf
  775 +
  776 +OR
  777 +
757 778 qpdf in.pdf --pages input-file [--password=password] [page-range] \
758 779 [...] -- out.pdf
759 780  
760 781 Between --pages and the -- that terminates pages option, repeat
761 782 the following:
762 783  
  784 +--file=filename [--range=page-range] [--password=password] [options]
  785 +
  786 +For compatibility, the file and range can be specified
  787 +positionally. qpdf versions prior to 11.9.0
  788 +require --password=password to immediately follow the filename. In
  789 +the older syntax, repeat the following:
  790 +
763 791 filename [--password=password] [page-range]
764 792  
765 793 Document-level information, such as outlines, tags, etc., is taken
... ... @@ -789,7 +817,7 @@ pages from b.pdf, and write the output to out.pdf. Document-level
789 817 information from in.pdf is retained. Note the use of "." to refer
790 818 to in.pdf.
791 819  
792   - qpdf in.pdf --pages . a.pdf b.pdf:even -- out.pdf
  820 + qpdf in.pdf --pages . a.pdf b.pdf 1-z:even -- out.pdf
793 821  
794 822 .IP \[bu]
795 823 Take all the pages from a.pdf, all the pages from b.pdf in
... ...
qpdf/qpdf.testcov
... ... @@ -249,7 +249,6 @@ QPDF not caching overridden objstm object 0
249 249 QPDFWriter original obj non-zero gen 0
250 250 QPDF_optimization indirect outlines 0
251 251 QPDF xref space 2
252   -QPDFJob pages range omitted at end 1
253 252 QPDFJob pages range omitted in middle 0
254 253 QPDFJob npages 0
255 254 QPDF already reserved object 0
... ... @@ -690,3 +689,5 @@ QPDF skipping cache for known unchecked object 0
690 689 QPDF fix dangling triggered xref reconstruction 0
691 690 QPDFPageDocumentHelper flatten resources missing or invalid 0
692 691 QPDF recover xref stream 0
  692 +QPDFJob misplaced page range 0
  693 +QPDFJob duplicated range 0
... ...
qpdf/qtest/arg-parsing.test
... ... @@ -15,7 +15,7 @@ cleanup();
15 15  
16 16 my $td = new TestDriver('arg-parsing');
17 17  
18   -my $n_tests = 24;
  18 +my $n_tests = 25;
19 19  
20 20 $td->runtest("required argument",
21 21 {$td->COMMAND => "qpdf --password minimal.pdf"},
... ... @@ -62,14 +62,20 @@ $td-&gt;runtest(&quot;bad file detected as unclosed --pages&quot;,
62 62 {$td->REGEXP => ".*pages options must be terminated with --.*",
63 63 $td->EXIT_STATUS => 2},
64 64 $td->NORMALIZE_NEWLINES);
65   -$td->runtest("misplaced pages password 1",
66   - {$td->COMMAND => "qpdf --pages . 1 --password=z --"},
67   - {$td->REGEXP => ".*password must immediately follow a file name.*",
  65 +$td->runtest("misplaced pages range",
  66 + {$td->COMMAND => "qpdf --pages --range=1 . --password=z --"},
  67 + {$td->REGEXP => ".*range must follow a file name.*",
68 68 $td->EXIT_STATUS => 2},
69 69 $td->NORMALIZE_NEWLINES);
70   -$td->runtest("misplaced pages password 2",
  70 +$td->runtest("duplicate pages range",
  71 + {$td->COMMAND => "qpdf --pages --file=." .
  72 + " --range=1 --range=2 . --password=z --"},
  73 + {$td->REGEXP => ".*range already specified.*",
  74 + $td->EXIT_STATUS => 2},
  75 + $td->NORMALIZE_NEWLINES);
  76 +$td->runtest("misplaced pages password",
71 77 {$td->COMMAND => "qpdf --pages --password=z . 1 --"},
72   - {$td->REGEXP => ".*password must immediately follow a file name.*",
  78 + {$td->REGEXP => ".*password must follow a file name.*",
73 79 $td->EXIT_STATUS => 2},
74 80 $td->NORMALIZE_NEWLINES);
75 81 $td->runtest("duplicated pages password",
... ...
qpdf/qtest/merge-and-split.test
... ... @@ -21,9 +21,9 @@ my $n_tests = 28;
21 21 # first time. The file 20-pages.pdf is specified with two different
22 22 # paths to duplicate a page.
23 23 my $pages_options = "--pages page-labels-and-outlines.pdf 1,3,5-7,z" .
24   - " 20-pages.pdf --password=user z-15" .
25   - " page-labels-and-outlines.pdf 12" .
26   - " 20-pages.pdf 10" .
  24 + " --file=20-pages.pdf --range=z-15 --password=user" .
  25 + " page-labels-and-outlines.pdf --range=12" .
  26 + " --file=20-pages.pdf 10" .
27 27 " ./20-pages.pdf --password=owner 10" .
28 28 " minimal.pdf 1 --";
29 29  
... ...