diff --git a/TODO b/TODO index be29c58e..f71528e1 100644 --- a/TODO +++ b/TODO @@ -9,12 +9,12 @@ Before Release: * Release qtest with updates to qtest-driver and copy back into qpdf Next: -* output capture * QPDFPagesTree -- avoid ever flattening the pages tree. * JSON v2 fixes Pending changes: +* Allow users to supply a custom progress reporter for QPDFJob * Check about runpath in the linux-bin distribution. I think the appimage build specifically is setting the runpath, which is actually desirable in this case. Make sure to understand and @@ -42,21 +42,6 @@ Pending changes: Soon: Break ground on "Document-level work" -Output Capture + QPDFJob -======================== - -QPDFJob: - -* Allow users to supply a custom progress reporter for QPDFJob - -Output Capture - -See https://github.com/qpdf/qpdf/issues/691 - -There needs to be a C API to QPDFLogger. Use functions like this: - -void set_info((*f)(char* data, unsigned int len, void* udata), void* udata); - QPDFPagesTree ============= diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 40ecd603..947068dd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,11 +14,13 @@ set(EXAMPLE_CXX_PROGRAMS pdf-parse-content pdf-set-form-values pdf-split-pages - qpdf-job) + qpdf-job + qpdfjob-save-attachment) set(EXAMPLE_C_PROGRAMS pdf-c-objects pdf-linearize - qpdfjob-c) + qpdfjob-c + qpdfjob-c-save-attachment) foreach(PROG ${EXAMPLE_CXX_PROGRAMS}) add_executable(${PROG} ${PROG}.cc) diff --git a/examples/qpdfjob-c-save-attachment.c b/examples/qpdfjob-c-save-attachment.c new file mode 100644 index 00000000..950e297b --- /dev/null +++ b/examples/qpdfjob-c-save-attachment.c @@ -0,0 +1,100 @@ +#include +#include +#include + +#include +#include +#include +#include + +// This example demonstrates how we can redirect where saved output +// goes by calling the default logger's setSave method before running +// something with QPDFJob. See qpdfjob-c-save-attachment.c for an +// implementation that uses the C API. + +static void +save_to_file(char const* data, size_t len, void* udata) +{ + FILE* f = (FILE*)udata; + fwrite(data, 1, len, f); +} + +static FILE* +do_fopen(char const* filename) +{ + FILE* f = NULL; +#ifdef _MSC_VER + if (fopen_s(&f, filename, "wb") != 0) { + f = NULL; + } +#else + f = fopen(filename, "wb"); +#endif + if (f == NULL) { + fprintf(stderr, "unable to open %s\n", filename); + exit(2); + } + return f; +} + +int +main(int argc, char* argv[]) +{ + char const* whoami = "qpdfjob-c-save-attachment"; + char const* filename = NULL; + char const* key = NULL; + char const* outfilename = NULL; + char* attachment_arg = NULL; + char const* attachment_flag = "--show-attachment="; + size_t flag_len = 0; + FILE* outfile = NULL; + int status = 0; + qpdfjob_handle j = NULL; + qpdflogger_handle l = qpdflogger_default_logger(); + + if (argc != 4) { + fprintf(stderr, "Usage: %s file attachment-key outfile\n", whoami); + exit(2); + } + + filename = argv[1]; + key = argv[2]; + outfilename = argv[3]; + + flag_len = strlen(attachment_flag) + strlen(key) + 1; + attachment_arg = malloc(flag_len); +#ifdef _MSC_VER + strncpy_s(attachment_arg, flag_len, attachment_flag, flag_len); + strncat_s(attachment_arg, flag_len, key, flag_len - strlen(attachment_arg)); +#else + strncpy(attachment_arg, attachment_flag, flag_len); + strncat(attachment_arg, key, flag_len - strlen(attachment_arg)); +#endif + + char const* j_argv[5] = { + whoami, + filename, + attachment_arg, + "--", + NULL, + }; + outfile = do_fopen(outfilename); + + /* Use qpdflogger_set_save with a callback function to redirect + * saved data. You can use other qpdf logger functions to capture + * informational output, warnings, and errors. + */ + qpdflogger_set_save( + l, qpdf_log_dest_custom, save_to_file, (void*)outfile, 0); + qpdflogger_cleanup(&l); + j = qpdfjob_init(); + status = (qpdfjob_initialize_from_argv(j, j_argv) || + qpdfjob_run(j)); + qpdfjob_cleanup(&j); + free(attachment_arg); + fclose(outfile); + if (status == qpdf_exit_success) { + printf("%s: wrote attachment to %s\n", whoami, outfilename); + } + return 0; +} diff --git a/examples/qpdfjob-c.c b/examples/qpdfjob-c.c index ee2ef4ab..452e689b 100644 --- a/examples/qpdfjob-c.c +++ b/examples/qpdfjob-c.c @@ -52,6 +52,10 @@ main(int argc, char* argv[]) * To use that from C just like the argv one, call * qpdfjob_run_from_json instead and pass the json string as a * single char const* argument. + * + * See qpdfjob-c-save-attachment.c for an example of using the + * full form of the qpdfjob interface with init and cleanup + * functions. */ r = qpdfjob_run_from_argv(new_argv); return r; diff --git a/examples/qpdfjob-save-attachment.cc b/examples/qpdfjob-save-attachment.cc new file mode 100644 index 00000000..2c08620a --- /dev/null +++ b/examples/qpdfjob-save-attachment.cc @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +// This example demonstrates how we can redirect where saved output +// goes by calling the default logger's setSave method before running +// something with QPDFJob. See qpdfjob-c-save-attachment.c for an +// implementation that uses the C API. + +int +main(int argc, char* argv[]) +{ + auto whoami = QUtil::getWhoami(argv[0]); + + if (argc != 4) { + std::cerr << "Usage: " << whoami << " file attachment-key outfile" + << std::endl; + exit(2); + } + + char const* filename = argv[1]; + char const* key = argv[2]; + char const* outfilename = argv[3]; + std::string attachment_arg = "--show-attachment="; + attachment_arg += key; + + char const* j_argv[] = { + whoami, + filename, + attachment_arg.c_str(), + "--", + nullptr, + }; + + QUtil::FileCloser fc(QUtil::safe_fopen(outfilename, "wb")); + auto save = std::make_shared("capture", fc.f); + QPDFLogger::defaultLogger()->setSave(save, false); + + try { + QPDFJob j; + j.initializeFromArgv(j_argv); + j.run(); + } catch (std::exception& e) { + std::cerr << whoami << ": " << e.what() << std::endl; + exit(2); + } + + std::cout << whoami << ": wrote attachment to " << outfilename << std::endl; + return 0; +} diff --git a/examples/qtest/qpdfjob-attachment/attach.pdf b/examples/qtest/qpdfjob-attachment/attach.pdf new file mode 100644 index 00000000..bbc09588 Binary files /dev/null and b/examples/qtest/qpdfjob-attachment/attach.pdf differ diff --git a/examples/qtest/save-attachment.test b/examples/qtest/save-attachment.test new file mode 100644 index 00000000..2486a29c --- /dev/null +++ b/examples/qtest/save-attachment.test @@ -0,0 +1,42 @@ +#!/usr/bin/env perl +require 5.008; +use warnings; +use strict; + +chdir("qpdfjob-attachment") or die "chdir testdir failed: $!\n"; + +require TestDriver; + +cleanup(); + +my $td = new TestDriver('qpdfjob-attachment'); + +$td->runtest("save attachment (C)", + {$td->COMMAND => "qpdfjob-c-save-attachment attach.pdf a a"}, + {$td->STRING => + "qpdfjob-c-save-attachment: wrote attachment to a\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a"}, + {$td->STRING => "quack\n"}, + $td->NORMALIZE_NEWLINES); +$td->runtest("save attachment (C++)", + {$td->COMMAND => "qpdfjob-save-attachment attach.pdf a b"}, + {$td->STRING => + "qpdfjob-save-attachment: wrote attachment to b\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "b"}, + {$td->STRING => "quack\n"}, + $td->NORMALIZE_NEWLINES); + +cleanup(); + +$td->report(4); + +sub cleanup +{ + unlink "a", "b"; +} diff --git a/include/qpdf/QPDFLogger.hh b/include/qpdf/QPDFLogger.hh index 54ab7efe..655da668 100644 --- a/include/qpdf/QPDFLogger.hh +++ b/include/qpdf/QPDFLogger.hh @@ -56,6 +56,10 @@ class QPDFLogger // error -- standard error // save -- undefined unless set // + // "info" is used for diagnostic messages, verbose messages, and + // progress messages. "warn" is used for warnings. "error" is used + // for errors. "save" is used for saving output -- see below. + // // On deletion, finish() is called for the standard output and // standard error pipelines, which flushes output. If you supply // any custom pipelines, you must call finish() on them yourself. @@ -64,6 +68,13 @@ class QPDFLogger // // NOTES ABOUT THE SAVE PIPELINE // + // The save pipeline is used by QPDFJob when some kind of binary + // output is being saved. This includes saving attachments and + // stream data and also includes when the output file is standard + // output. If you want to grab that output, you can call setSave. + // See examples/qpdfjob-save-attachment.cc and + // examples/qpdfjob-c-save-attachment.c. + // // You should never set the save pipeline to the same destination // as something else. Doing so will corrupt your save output. If // you want to save to standard output, use the method diff --git a/include/qpdf/qpdflogger-c.h b/include/qpdf/qpdflogger-c.h index 9d31f79c..10ab5bb0 100644 --- a/include/qpdf/qpdflogger-c.h +++ b/include/qpdf/qpdflogger-c.h @@ -25,7 +25,8 @@ /* * This file provides a C API for QPDFLogger. See QPDFLogger.hh for - * information about the logger. + * information about the logger and + * examples/qpdfjob-c-save-attachment.c for an example. */ #include diff --git a/manual/release-notes.rst b/manual/release-notes.rst index f0e54db9..9a911eea 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -160,6 +160,9 @@ For a detailed list of changes, please see the file - A C API is available in :file:`include/qpdf/qpdflogger-c.h`. + - See examples :file:`examples/qpdfjob-save-attachment.cc` and + :file:`examples/qpdfjob-c-save-attachment.cc`. + - New methods ``insertItemAndGet``, ``appendItemAndGet``, ``eraseItemAndGet``, ``replaceKeyAndGet``, and ``removeKeyAndGet`` return the newly added or removed object.