From a2fc5b522e2bacf0eb0cc165388a63a19f7e1fdc Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sun, 2 Feb 2025 14:15:00 -0500 Subject: [PATCH] fix-qdf: accept optional output file (fixes #1330) --- ChangeLog | 5 +++++ manual/fix-qdf.1.in | 6 ++++-- manual/qdf.rst | 16 ++++++++++++---- manual/release-notes.rst | 8 ++++++++ qpdf/fix-qdf.cc | 48 ++++++++++++++++++++++++++++++++++-------------- qpdf/qtest/fix-qdf.test | 21 ++++++++++++++++++++- 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 10f1adf..3a7b5a7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2025-02-02 Jay Berkenbilt + + * Have fix-qdf accept a second argument, interpreted as the output + file. Fixes #1330. + 2024-02-01 M Holger * Bug fix: in qpdf CLI / QPDFJob throw a QPDFUsage exception if a diff --git a/manual/fix-qdf.1.in b/manual/fix-qdf.1.in index 9cbd6fa..0c5d1a2 100644 --- a/manual/fix-qdf.1.in +++ b/manual/fix-qdf.1.in @@ -3,9 +3,11 @@ fix-qdf \- repair PDF files in QDF form after editing .SH SYNOPSIS .B fix-qdf -< \fIinfilename\fR > \fIoutfilename\fR +[\fIinfilename\fR [\fIoutfilename\fR]] .SH DESCRIPTION -The fix-qdf program is part of the qpdf package. +The fix-qdf program is part of the qpdf package. With no arguments, +fix-qdf reads from standard input and writes to standard output. With +one argument, it reads from that file and writes to standard output. .PP The fix-qdf program reads a PDF file in QDF form and writes out the same file with stream lengths, cross-reference table entries, and diff --git a/manual/qdf.rst b/manual/qdf.rst index d1bf4b6..96cfc28 100644 --- a/manual/qdf.rst +++ b/manual/qdf.rst @@ -23,10 +23,18 @@ files are full of offset and length information that makes it hard to add or remove data. A QDF file is organized in a manner such that, if edits are kept within certain constraints, the :command:`fix-qdf` program, distributed with qpdf, is -able to restore edited files to a correct state. The -:command:`fix-qdf` program takes no command-line -arguments. It reads a possibly edited QDF file from standard input and -writes a repaired file to standard output. +able to restore edited files to a correct state. + +.. code-block:: bash + + fix-qdf [infilename [outfilename]] + +With no arguments, :command:`fix-qdf` reads the possibly-edited QDF +file from standard input and writes a repaired file to standard +output. You can also specify the input and output files as +command-line arguments. With one argument, the argument is taken as an +input file. With two arguments, the first argument is an input file, +and the second is an output file. For another way to work with PDF files in an editor, see :ref:`json`. Using qpdf JSON format allows you to edit the PDF file semantically diff --git a/manual/release-notes.rst b/manual/release-notes.rst index 76faa39..26fc535 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -38,6 +38,14 @@ Planned changes for future 12.x (subject to change): .. x.y.z: not yet released +11.10.0: not yet released + - CLI Enhancements + + - The :command:`fix-qdf` command now allows an output file to be + specified as an optional second argument. This is useful for + environments in which writing a binary file to standard output + doesn't work (such as PowerShell 5). + 11.9.1: June 7, 2024 - Bug Fixes diff --git a/qpdf/fix-qdf.cc b/qpdf/fix-qdf.cc index 57c4360..1770ddd 100644 --- a/qpdf/fix-qdf.cc +++ b/qpdf/fix-qdf.cc @@ -4,17 +4,20 @@ #include #include #include +#include #include #include #include +using namespace std::literals; static char const* whoami = nullptr; static void usage() { - std::cerr << "Usage: " << whoami << " [filename]" << std::endl; - exit(2); + std::cerr << "Usage: " << whoami << " [infilename [outfilename]]" << std::endl + << "infilename defaults to standard output" << std::endl + << "outfilename defaults to standard output" << std::endl; } class QdfFixer @@ -373,27 +376,44 @@ realmain(int argc, char* argv[]) whoami = QUtil::getWhoami(argv[0]); QUtil::setLineBuf(stdout); char const* filename = nullptr; - if (argc > 2) { + char const* outfilename = nullptr; + if (argc > 3) { usage(); } else if ((argc > 1) && (strcmp(argv[1], "--version") == 0)) { std::cout << whoami << " from qpdf version " << QPDF::QPDFVersion() << std::endl; return 0; } else if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) { usage(); - } else if (argc == 2) { + } else if (argc >= 2) { filename = argv[1]; + if (argc == 3) { + outfilename = argv[2]; + } } - std::string input; - if (filename == nullptr) { - filename = "standard input"; - QUtil::binary_stdin(); - input = QUtil::read_file_into_string(stdin); - } else { - input = QUtil::read_file_into_string(filename); + try { + std::string input; + if (filename == nullptr) { + filename = "standard input"; + QUtil::binary_stdin(); + input = QUtil::read_file_into_string(stdin); + } else { + input = QUtil::read_file_into_string(filename); + } + std::unique_ptr out = nullptr; + if (outfilename) { + out = std::make_unique(outfilename, std::ios::binary); + if (out->fail()) { + QUtil::throw_system_error("open "s + outfilename); + } + } else { + QUtil::binary_stdout(); + } + QdfFixer qf(filename, out ? *out : std::cout); + qf.processLines(input); + } catch (std::exception& e) { + std::cerr << whoami << ": error: " << e.what() << std::endl; + exit(qpdf_exit_error); } - QUtil::binary_stdout(); - QdfFixer qf(filename, std::cout); - qf.processLines(input); return 0; } diff --git a/qpdf/qtest/fix-qdf.test b/qpdf/qtest/fix-qdf.test index 96f4236..af5f565 100644 --- a/qpdf/qtest/fix-qdf.test +++ b/qpdf/qtest/fix-qdf.test @@ -14,7 +14,7 @@ cleanup(); my $td = new TestDriver('fix-qdf'); -my $n_tests = 5; +my $n_tests = 11; for (my $n = 1; $n <= 2; ++$n) { @@ -23,6 +23,15 @@ for (my $n = 1; $n <= 2; ++$n) {$td->FILE => "fix$n.qdf.out", $td->EXIT_STATUS => 0}); + $td->runtest("fix-qdf $n with named output", + {$td->COMMAND => "fix-qdf fix$n.qdf a.pdf"}, + {$td->STRING => "", + $td->EXIT_STATUS => 0}); + + $td->runtest("check fix-qdf $n output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "fix$n.qdf.out"}); + $td->runtest("identity fix-qdf $n", {$td->COMMAND => "fix-qdf fix$n.qdf.out"}, {$td->FILE => "fix$n.qdf.out", @@ -54,5 +63,15 @@ $td->runtest("fix-qdf with big object stream", # > 255 objects in a stream {$td->FILE => "big-ostream.pdf", $td->EXIT_STATUS => 0}); +$td->runtest("fix-qdf error opening input", + {$td->COMMAND => "fix-qdf /does/not/exist/potato.pdf"}, + {$td->REGEXP => "^fix-qdf: error: open .*/does/not/exist/potato.pdf: .*", + $td->EXIT_STATUS => 2}); + +$td->runtest("fix-qdf error opening output", # > 255 objects in a stream + {$td->COMMAND => "fix-qdf fix1.qdf /does/not/exist/salad.pdf"}, + {$td->REGEXP => "^fix-qdf: error: open .*/does/not/exist/salad.pdf: .*", + $td->EXIT_STATUS => 2}); + cleanup(); $td->report($n_tests); -- libgit2 0.21.4