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,7 +40,9 @@ main(int argc, char* argv[])
40 ->inputFile("in.pdf") 40 ->inputFile("in.pdf")
41 ->outputFile("out1.pdf") 41 ->outputFile("out1.pdf")
42 ->pages() 42 ->pages()
43 - ->pageSpec(".", "1") 43 + // Prior to qpdf 11.9.0, call ->pageSpec(file, range, password)
  44 + ->file(".")
  45 + ->range("1")
44 ->endPages() 46 ->endPages()
45 ->linearize() 47 ->linearize()
46 ->staticId() // for testing only 48 ->staticId() // for testing only
include/qpdf/QPDFJob.hh
@@ -243,6 +243,8 @@ class QPDFJob @@ -243,6 +243,8 @@ class QPDFJob
243 public: 243 public:
244 QPDF_DLL 244 QPDF_DLL
245 Config* endPages(); 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 QPDF_DLL 248 QPDF_DLL
247 PagesConfig* pageSpec( 249 PagesConfig* pageSpec(
248 std::string const& filename, std::string const& range, char const* password = nullptr); 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,3 +5,6 @@
5 // 5 //
6 // clang-format off 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,16 +5,16 @@ include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf2
5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
7 include/qpdf/auto_job_c_main.hh dbfc221d1533120d1aa9c361d8d2483dea5fcb1c0fd95144d98d305e64ed32a6 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 include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1 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 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 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 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b 20 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
@@ -195,9 +195,9 @@ options: @@ -195,9 +195,9 @@ options:
195 config: c_pages 195 config: c_pages
196 prefix: Pages 196 prefix: Pages
197 positional: true 197 positional: true
198 - manual:  
199 - - password  
200 required_parameter: 198 required_parameter:
  199 + file: file
  200 + range: page-range
201 password: password 201 password: password
202 - table: encryption 202 - table: encryption
203 config: c_main 203 config: c_main
@@ -436,9 +436,9 @@ json: @@ -436,9 +436,9 @@ json:
436 oi-min-width: 436 oi-min-width:
437 optimize-images: 437 optimize-images:
438 pages: 438 pages:
439 - - _file: "source for for pages" 439 + - file:
440 Pages.password: 440 Pages.password:
441 - _range: "page range" 441 + range:
442 remove-page-labels: 442 remove-page-labels:
443 report-memory-usage: 443 report-memory-usage:
444 rotate: 444 rotate:
libqpdf/QPDFJob.cc
@@ -2342,6 +2342,9 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea @@ -2342,6 +2342,9 @@ QPDFJob::handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_hea
2342 if (page_spec.filename == ".") { 2342 if (page_spec.filename == ".") {
2343 page_spec.filename = m->infilename.get(); 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 if (!m->keep_files_open_set) { 2350 if (!m->keep_files_open_set) {
libqpdf/QPDFJob_argv.cc
@@ -34,9 +34,10 @@ namespace @@ -34,9 +34,10 @@ namespace
34 std::shared_ptr<QPDFJob::UOConfig> c_uo; 34 std::shared_ptr<QPDFJob::UOConfig> c_uo;
35 std::shared_ptr<QPDFJob::EncConfig> c_enc; 35 std::shared_ptr<QPDFJob::EncConfig> c_enc;
36 std::vector<std::string> accumulated_args; 36 std::vector<std::string> accumulated_args;
37 - std::shared_ptr<char> pages_password{nullptr};  
38 std::string user_password; 37 std::string user_password;
39 std::string owner_password; 38 std::string owner_password;
  39 + bool called_pages_file{false};
  40 + bool called_pages_range{false};
40 bool used_enc_password_args{false}; 41 bool used_enc_password_args{false};
41 bool gave_input{false}; 42 bool gave_input{false};
42 bool gave_output{false}; 43 bool gave_output{false};
@@ -237,81 +238,43 @@ ArgParser::argPages() @@ -237,81 +238,43 @@ ArgParser::argPages()
237 } 238 }
238 239
239 void 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 ArgParser::argPagesPositional(std::string const& arg) 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 } else { 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 void 275 void
312 ArgParser::argEndPages() 276 ArgParser::argEndPages()
313 { 277 {
314 - argPagesPositional("");  
315 c_pages->endPages(); 278 c_pages->endPages();
316 c_pages = nullptr; 279 c_pages = nullptr;
317 } 280 }
libqpdf/QPDFJob_config.cc
@@ -968,6 +968,45 @@ QPDFJob::PagesConfig::pageSpec( @@ -968,6 +968,45 @@ QPDFJob::PagesConfig::pageSpec(
968 return this; 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 std::shared_ptr<QPDFJob::UOConfig> 1010 std::shared_ptr<QPDFJob::UOConfig>
972 QPDFJob::Config::overlay() 1011 QPDFJob::Config::overlay()
973 { 1012 {
libqpdf/QPDFJob_json.cc
@@ -467,25 +467,17 @@ Handlers::endPagesArray() @@ -467,25 +467,17 @@ Handlers::endPagesArray()
467 void 467 void
468 Handlers::beginPages(JSON j) 468 Handlers::beginPages(JSON j)
469 { 469 {
470 - std::string file;  
471 - std::string range("1-z");  
472 - std::string password;  
473 bool file_seen = false; 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 if (key == "file") { 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 if (!file_seen) { 477 if (!file_seen) {
485 QTC::TC("qpdf", "QPDFJob json pages no file"); 478 QTC::TC("qpdf", "QPDFJob json pages no file");
486 usage("file is required in page specification"); 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 void 483 void
@@ -495,24 +487,9 @@ Handlers::endPages() @@ -495,24 +487,9 @@ Handlers::endPages()
495 } 487 }
496 488
497 void 489 void
498 -Handlers::setupPagesFile()  
499 -{  
500 - // handled in beginPages  
501 - ignoreItem();  
502 -}  
503 -  
504 -void  
505 Handlers::setupPagesPassword() 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 void 495 void
libqpdf/qpdf/auto_job_decl.hh
@@ -31,7 +31,6 @@ void argReplaceInput(); @@ -31,7 +31,6 @@ void argReplaceInput();
31 void argSetPageLabels(); 31 void argSetPageLabels();
32 void argUnderlay(); 32 void argUnderlay();
33 void argPagesPositional(std::string const&); 33 void argPagesPositional(std::string const&);
34 -void argPagesPassword(std::string const&);  
35 void argEndPages(); 34 void argEndPages();
36 void argEncPositional(std::string const&); 35 void argEncPositional(std::string const&);
37 void argEncUserPassword(std::string const&); 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,10 +311,24 @@ static void add_help_4(QPDFArgParser&amp; ap)
311 ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of 311 ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of
312 the PDF, causing the PDF to render differently from the original. 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 Run qpdf --help=page-selection for details. 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 ap.addOptionHelp("--collate", "modification", "collate with --pages", R"(--collate[=n[,m,...]] 332 ap.addOptionHelp("--collate", "modification", "collate with --pages", R"(--collate[=n[,m,...]]
319 333
320 Collate rather than concatenate pages specified with --pages. 334 Collate rather than concatenate pages specified with --pages.
@@ -437,6 +451,9 @@ iv, then the remaining pages with Arabic numerals starting with @@ -437,6 +451,9 @@ iv, then the remaining pages with Arabic numerals starting with
437 1 and continuing sequentially until the end of the document. For 451 1 and continuing sequentially until the end of the document. For
438 additional examples, please consult the manual. 452 additional examples, please consult the manual.
439 )"); 453 )");
  454 +}
  455 +static void add_help_5(QPDFArgParser& ap)
  456 +{
440 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage: 457 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage:
441 458
442 --encrypt \ 459 --encrypt \
@@ -520,9 +537,6 @@ ap.addOptionHelp(&quot;--user-password&quot;, &quot;encryption&quot;, &quot;specify user password&quot;, R&quot;(-- @@ -520,9 +537,6 @@ ap.addOptionHelp(&quot;--user-password&quot;, &quot;encryption&quot;, &quot;specify user password&quot;, R&quot;(--
520 537
521 Set the user password of the encrypted file. 538 Set the user password of the encrypted file.
522 )"); 539 )");
523 -}  
524 -static void add_help_5(QPDFArgParser& ap)  
525 -{  
526 ap.addOptionHelp("--owner-password", "encryption", "specify owner password", R"(--owner-password=owner-password 540 ap.addOptionHelp("--owner-password", "encryption", "specify owner password", R"(--owner-password=owner-password
527 541
528 Set the owner password of the encrypted file. 542 Set the owner password of the encrypted file.
@@ -620,12 +634,24 @@ should not be used except for compatibility testing. @@ -620,12 +634,24 @@ should not be used except for compatibility testing.
620 )"); 634 )");
621 ap.addHelpTopic("page-selection", "select pages from one or more files", R"(Use the --pages option to select pages from multiple files. Usage: 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 qpdf in.pdf --pages input-file [--password=password] [page-range] \ 642 qpdf in.pdf --pages input-file [--password=password] [page-range] \
624 [...] -- out.pdf 643 [...] -- out.pdf
625 644
626 Between --pages and the -- that terminates pages option, repeat 645 Between --pages and the -- that terminates pages option, repeat
627 the following: 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 filename [--password=password] [page-range] 655 filename [--password=password] [page-range]
630 656
631 Document-level information, such as outlines, tags, etc., is taken 657 Document-level information, such as outlines, tags, etc., is taken
@@ -654,7 +680,7 @@ Examples: @@ -654,7 +680,7 @@ Examples:
654 information from in.pdf is retained. Note the use of "." to refer 680 information from in.pdf is retained. Note the use of "." to refer
655 to in.pdf. 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 - Take all the pages from a.pdf, all the pages from b.pdf in 685 - Take all the pages from a.pdf, all the pages from b.pdf in
660 reverse, and only pages 3 and 6 from c.pdf and write the result 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,6 +713,9 @@ those pages to be repeated after the original pages are exhausted.
687 713
688 Run qpdf --help=page-ranges for help with page ranges. 714 Run qpdf --help=page-ranges for help with page ranges.
689 )"); 715 )");
  716 +}
  717 +static void add_help_6(QPDFArgParser& ap)
  718 +{
690 ap.addOptionHelp("--to", "overlay-underlay", "destination pages for underlay/overlay", R"(--to=page-range 719 ap.addOptionHelp("--to", "overlay-underlay", "destination pages for underlay/overlay", R"(--to=page-range
691 720
692 Specify the range of pages in the primary output to apply 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,9 +729,6 @@ the destination pages. See qpdf --help=page-ranges for help
700 with the page range syntax. The page range may be omitted 729 with the page range syntax. The page range may be omitted
701 if --repeat is used. 730 if --repeat is used.
702 )"); 731 )");
703 -}  
704 -static void add_help_6(QPDFArgParser& ap)  
705 -{  
706 ap.addOptionHelp("--repeat", "overlay-underlay", "overlay/underlay pages to repeat", R"(--repeat=page-range 732 ap.addOptionHelp("--repeat", "overlay-underlay", "overlay/underlay pages to repeat", R"(--repeat=page-range
707 733
708 Specify pages from the overlay/underlay that are repeated after 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,6 +827,9 @@ ap.addHelpTopic(&quot;inspection&quot;, &quot;inspect PDF files&quot;, R&quot;(These options provide tool
801 the options in this section are specified, no output file may be 827 the options in this section are specified, no output file may be
802 given. 828 given.
803 )"); 829 )");
  830 +}
  831 +static void add_help_7(QPDFArgParser& ap)
  832 +{
804 ap.addOptionHelp("--is-encrypted", "inspection", "silently test whether a file is encrypted", R"(Silently exit with a code indicating the file's encryption status: 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 0: the file is encrypted 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,9 +846,6 @@ ap.addOptionHelp(&quot;--requires-password&quot;, &quot;inspection&quot;, &quot;silently test a file&#39;s pa
817 2: the file is not encrypted 846 2: the file is not encrypted
818 3: the file is encrypted, and correct password (if any) has been supplied 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 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 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 aspects of the file, and write information about the file to 850 aspects of the file, and write information about the file to
825 standard output. Note that qpdf does not perform any validation 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,6 +920,9 @@ Describe the format of the JSON output by writing to standard
894 output a JSON object with the same keys and with values 920 output a JSON object with the same keys and with values
895 containing descriptive text. 921 containing descriptive text.
896 )"); 922 )");
  923 +}
  924 +static void add_help_8(QPDFArgParser& ap)
  925 +{
897 ap.addOptionHelp("--json-key", "json", "limit which keys are in JSON output", R"(--json-key=key 926 ap.addOptionHelp("--json-key", "json", "limit which keys are in JSON output", R"(--json-key=key
898 927
899 This option is repeatable. If given, only the specified 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,9 +936,6 @@ This option is repeatable. If given, only specified objects will
907 be shown in the "objects" key of the JSON output. Otherwise, all 936 be shown in the "objects" key of the JSON output. Otherwise, all
908 objects will be shown. 937 objects will be shown.
909 )"); 938 )");
910 -}  
911 -static void add_help_8(QPDFArgParser& ap)  
912 -{  
913 ap.addOptionHelp("--json-stream-data", "json", "how to handle streams in json output", R"(--json-stream-data={none|inline|file} 939 ap.addOptionHelp("--json-stream-data", "json", "how to handle streams in json output", R"(--json-stream-data={none|inline|file}
914 940
915 When used with --json, this option controls whether streams in 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,7 +127,9 @@ this-&gt;ap.addChoices(&quot;json&quot;, [this](std::string const&amp; x){c_main-&gt;json(x);}, fals
127 this->ap.addChoices("json-output", [this](std::string const& x){c_main->jsonOutput(x);}, false, json_output_choices); 127 this->ap.addChoices("json-output", [this](std::string const& x){c_main->jsonOutput(x);}, false, json_output_choices);
128 this->ap.registerOptionTable("pages", b(&ArgParser::argEndPages)); 128 this->ap.registerOptionTable("pages", b(&ArgParser::argEndPages));
129 this->ap.addPositional(p(&ArgParser::argPagesPositional)); 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 this->ap.registerOptionTable("encryption", b(&ArgParser::argEndEncryption)); 133 this->ap.registerOptionTable("encryption", b(&ArgParser::argEndEncryption));
132 this->ap.addPositional(p(&ArgParser::argEncPositional)); 134 this->ap.addPositional(p(&ArgParser::argEncPositional));
133 this->ap.addRequiredParameter("user-password", p(&ArgParser::argEncUserPassword), "user_password"); 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,9 +41,7 @@ void beginPagesArray(JSON);
41 void endPagesArray(); 41 void endPagesArray();
42 void beginPages(JSON); 42 void beginPages(JSON);
43 void endPages(); 43 void endPages();
44 -void setupPagesFile();  
45 void setupPagesPassword(); 44 void setupPagesPassword();
46 -void setupPagesRange();  
47 void beginSetPageLabelsArray(JSON); 45 void beginSetPageLabelsArray(JSON);
48 void endSetPageLabelsArray(); 46 void endSetPageLabelsArray();
49 void setupSetPageLabels(); 47 void setupSetPageLabels();
libqpdf/qpdf/auto_job_json_init.hh
@@ -402,13 +402,13 @@ pushKey(&quot;pages&quot;); @@ -402,13 +402,13 @@ pushKey(&quot;pages&quot;);
402 beginArray(bindJSON(&Handlers::beginPagesArray), bindBare(&Handlers::endPagesArray)); // .pages[] 402 beginArray(bindJSON(&Handlers::beginPagesArray), bindBare(&Handlers::endPagesArray)); // .pages[]
403 beginDict(bindJSON(&Handlers::beginPages), bindBare(&Handlers::endPages)); // .pages 403 beginDict(bindJSON(&Handlers::beginPages), bindBare(&Handlers::endPages)); // .pages
404 pushKey("file"); 404 pushKey("file");
405 -setupPagesFile(); 405 +addParameter([this](std::string const& p) { c_pages->file(p); });
406 popHandler(); // key: file 406 popHandler(); // key: file
407 pushKey("password"); 407 pushKey("password");
408 setupPagesPassword(); 408 setupPagesPassword();
409 popHandler(); // key: password 409 popHandler(); // key: password
410 pushKey("range"); 410 pushKey("range");
411 -setupPagesRange(); 411 +addParameter([this](std::string const& p) { c_pages->range(p); });
412 popHandler(); // key: range 412 popHandler(); // key: range
413 popHandler(); // array: .pages[] 413 popHandler(); // array: .pages[]
414 popHandler(); // key: pages 414 popHandler(); // key: pages
libqpdf/qpdf/auto_job_schema.hh
@@ -140,7 +140,7 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({ @@ -140,7 +140,7 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({
140 "optimizeImages": "use efficient compression for images", 140 "optimizeImages": "use efficient compression for images",
141 "pages": [ 141 "pages": [
142 { 142 {
143 - "file": "source for for pages", 143 + "file": "source for pages",
144 "password": "password for encrypted file", 144 "password": "password for encrypted file",
145 "range": "page range" 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,7 +1388,7 @@ PDF, causing the PDF to render differently from the original. See also
1388 Related Options 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 .. help: begin page selection 1393 .. help: begin page selection
1394 1394
@@ -1403,6 +1403,38 @@ Related Options @@ -1403,6 +1403,38 @@ Related Options
1403 See also :qpdf:ref:`--split-pages`, :qpdf:ref:`--collate`, 1403 See also :qpdf:ref:`--split-pages`, :qpdf:ref:`--collate`,
1404 :ref:`page-ranges`. 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 .. qpdf:option:: --collate[=n[,m,...]] 1438 .. qpdf:option:: --collate[=n[,m,...]]
1407 1439
1408 .. help: collate with --pages 1440 .. help: collate with --pages
@@ -2424,12 +2456,24 @@ Page Selection @@ -2424,12 +2456,24 @@ Page Selection
2424 2456
2425 Use the --pages option to select pages from multiple files. Usage: 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 qpdf in.pdf --pages input-file [--password=password] [page-range] \ 2464 qpdf in.pdf --pages input-file [--password=password] [page-range] \
2428 [...] -- out.pdf 2465 [...] -- out.pdf
2429 2466
2430 Between --pages and the -- that terminates pages option, repeat 2467 Between --pages and the -- that terminates pages option, repeat
2431 the following: 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 filename [--password=password] [page-range] 2477 filename [--password=password] [page-range]
2434 2478
2435 Document-level information, such as outlines, tags, etc., is taken 2479 Document-level information, such as outlines, tags, etc., is taken
@@ -2458,7 +2502,7 @@ Page Selection @@ -2458,7 +2502,7 @@ Page Selection
2458 information from in.pdf is retained. Note the use of "." to refer 2502 information from in.pdf is retained. Note the use of "." to refer
2459 to in.pdf. 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 - Take all the pages from a.pdf, all the pages from b.pdf in 2507 - Take all the pages from a.pdf, all the pages from b.pdf in
2464 reverse, and only pages 3 and 6 from c.pdf and write the result 2508 reverse, and only pages 3 and 6 from c.pdf and write the result
@@ -2472,14 +2516,29 @@ Page Selection @@ -2472,14 +2516,29 @@ Page Selection
2472 split and merge PDF files by selecting pages from one or more input 2516 split and merge PDF files by selecting pages from one or more input
2473 files. 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 Notes: 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 - The password option is needed only for password-protected files. 2542 - The password option is needed only for password-protected files.
2484 If you specify the same file more than once, you only need to supply 2543 If you specify the same file more than once, you only need to supply
2485 the password the first time. 2544 the password the first time.
@@ -2518,8 +2577,7 @@ Examples @@ -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 - Take all the pages from :file:`a.pdf`, all the pages from 2582 - Take all the pages from :file:`a.pdf`, all the pages from
2525 :file:`b.pdf` in reverse, and only pages 3 and 6 from :file:`c.pdf` 2583 :file:`b.pdf` in reverse, and only pages 3 and 6 from :file:`c.pdf`
@@ -2529,7 +2587,9 @@ Examples @@ -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 - Scan a document with double-sided printing by scanning the fronts 2594 - Scan a document with double-sided printing by scanning the fronts
2535 into :file:`odd.pdf` and the backs into :file:`even.pdf`. Collate 2595 into :file:`odd.pdf` and the backs into :file:`even.pdf`. Collate
@@ -2542,6 +2602,8 @@ Examples @@ -2542,6 +2602,8 @@ Examples
2542 qpdf --collate odd.pdf --pages . even.pdf -- all.pdf 2602 qpdf --collate odd.pdf --pages . even.pdf -- all.pdf
2543 OR 2603 OR
2544 qpdf --collate --empty --pages odd.pdf even.pdf -- all.pdf 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 - When collating, any number of files and page ranges can be 2608 - When collating, any number of files and page ranges can be
2547 specified. If any file has fewer pages, that file is just skipped 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,10 +409,26 @@ the PDF, causing the PDF to render differently from the original.
409 Related Options: 409 Related Options:
410 .TP 410 .TP
411 .B --pages \-\- begin page selection 411 .B --pages \-\- begin page selection
412 ---pages file [--password=password] [page-range] [...] -- 412 +--pages [--file=]file [options] [...] --
413 413
414 Run qpdf --help=page-selection for details. 414 Run qpdf --help=page-selection for details.
415 .TP 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 .B --collate \-\- collate with --pages 432 .B --collate \-\- collate with --pages
417 --collate[=n[,m,...]] 433 --collate[=n[,m,...]]
418 434
@@ -754,12 +770,24 @@ should not be used except for compatibility testing. @@ -754,12 +770,24 @@ should not be used except for compatibility testing.
754 .SH PAGE-SELECTION (select pages from one or more files) 770 .SH PAGE-SELECTION (select pages from one or more files)
755 Use the --pages option to select pages from multiple files. Usage: 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 qpdf in.pdf --pages input-file [--password=password] [page-range] \ 778 qpdf in.pdf --pages input-file [--password=password] [page-range] \
758 [...] -- out.pdf 779 [...] -- out.pdf
759 780
760 Between --pages and the -- that terminates pages option, repeat 781 Between --pages and the -- that terminates pages option, repeat
761 the following: 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 filename [--password=password] [page-range] 791 filename [--password=password] [page-range]
764 792
765 Document-level information, such as outlines, tags, etc., is taken 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,7 +817,7 @@ pages from b.pdf, and write the output to out.pdf. Document-level
789 information from in.pdf is retained. Note the use of "." to refer 817 information from in.pdf is retained. Note the use of "." to refer
790 to in.pdf. 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 .IP \[bu] 822 .IP \[bu]
795 Take all the pages from a.pdf, all the pages from b.pdf in 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,7 +249,6 @@ QPDF not caching overridden objstm object 0
249 QPDFWriter original obj non-zero gen 0 249 QPDFWriter original obj non-zero gen 0
250 QPDF_optimization indirect outlines 0 250 QPDF_optimization indirect outlines 0
251 QPDF xref space 2 251 QPDF xref space 2
252 -QPDFJob pages range omitted at end 1  
253 QPDFJob pages range omitted in middle 0 252 QPDFJob pages range omitted in middle 0
254 QPDFJob npages 0 253 QPDFJob npages 0
255 QPDF already reserved object 0 254 QPDF already reserved object 0
@@ -690,3 +689,5 @@ QPDF skipping cache for known unchecked object 0 @@ -690,3 +689,5 @@ QPDF skipping cache for known unchecked object 0
690 QPDF fix dangling triggered xref reconstruction 0 689 QPDF fix dangling triggered xref reconstruction 0
691 QPDFPageDocumentHelper flatten resources missing or invalid 0 690 QPDFPageDocumentHelper flatten resources missing or invalid 0
692 QPDF recover xref stream 0 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,7 +15,7 @@ cleanup();
15 15
16 my $td = new TestDriver('arg-parsing'); 16 my $td = new TestDriver('arg-parsing');
17 17
18 -my $n_tests = 24; 18 +my $n_tests = 25;
19 19
20 $td->runtest("required argument", 20 $td->runtest("required argument",
21 {$td->COMMAND => "qpdf --password minimal.pdf"}, 21 {$td->COMMAND => "qpdf --password minimal.pdf"},
@@ -62,14 +62,20 @@ $td-&gt;runtest(&quot;bad file detected as unclosed --pages&quot;, @@ -62,14 +62,20 @@ $td-&gt;runtest(&quot;bad file detected as unclosed --pages&quot;,
62 {$td->REGEXP => ".*pages options must be terminated with --.*", 62 {$td->REGEXP => ".*pages options must be terminated with --.*",
63 $td->EXIT_STATUS => 2}, 63 $td->EXIT_STATUS => 2},
64 $td->NORMALIZE_NEWLINES); 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 $td->EXIT_STATUS => 2}, 68 $td->EXIT_STATUS => 2},
69 $td->NORMALIZE_NEWLINES); 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 {$td->COMMAND => "qpdf --pages --password=z . 1 --"}, 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 $td->EXIT_STATUS => 2}, 79 $td->EXIT_STATUS => 2},
74 $td->NORMALIZE_NEWLINES); 80 $td->NORMALIZE_NEWLINES);
75 $td->runtest("duplicated pages password", 81 $td->runtest("duplicated pages password",
qpdf/qtest/merge-and-split.test
@@ -21,9 +21,9 @@ my $n_tests = 28; @@ -21,9 +21,9 @@ my $n_tests = 28;
21 # first time. The file 20-pages.pdf is specified with two different 21 # first time. The file 20-pages.pdf is specified with two different
22 # paths to duplicate a page. 22 # paths to duplicate a page.
23 my $pages_options = "--pages page-labels-and-outlines.pdf 1,3,5-7,z" . 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 " ./20-pages.pdf --password=owner 10" . 27 " ./20-pages.pdf --password=owner 10" .
28 " minimal.pdf 1 --"; 28 " minimal.pdf 1 --";
29 29