diff --git a/ChangeLog b/ChangeLog index 454cf44c..77447a38 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2011-08-11 Jay Berkenbilt + + * include/qpdf/qpdf-c.h ("C"): add new methods qpdf_get_info_key + and qpdf_set_info_key for manipulating text fields of the /Info + dictionary. + 2011-08-10 Jay Berkenbilt * libqpdf/QPDFWriter.cc (copyEncryptionParameters): preserve diff --git a/TODO b/TODO index 59864149..61848ba5 100644 --- a/TODO +++ b/TODO @@ -3,10 +3,6 @@ * Provide an example of using replace and swap. Maybe. - * Figure out a way to update the C API with something that can - update dictionary keys at least with strings. Make sure it is - possible to implement something akin to pdf-mod-info in C. - * Add C API for writing to memory if possible General diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index 34c21bec..98a6f07b 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -199,18 +199,46 @@ extern "C" { /* Read functions below must be called after qpdf_read or * qpdf_read_memory. */ - /* Return the version of the PDF file. */ + /* + * NOTE: Functions that return char* are returning a pointer to an + * internal buffer that will be reused for each call to a function + * that returns a char*. You must use or copy the value before + * calling any other qpdf library functions. + */ + + /* Return the version of the PDF file. See warning above about + * functions that return char*. */ QPDF_DLL char const* qpdf_get_pdf_version(qpdf_data qpdf); /* Return the user password. If the file is opened using the * owner password, the user password may be retrieved using this * function. If the file is opened using the user password, this - * function will return that user password. + * function will return that user password. See warning above + * about functions that return char*. */ QPDF_DLL char const* qpdf_get_user_password(qpdf_data qpdf); + /* Return the string value of a key in the document's Info + * dictionary. The key parameter should include the leading + * slash, e.g. "/Author". If the key is not present or has a + * non-string value, a null pointer is returned. Otherwise, a + * pointer to an internal buffer is returned. See warning above + * about functions that return char*. + */ + QPDF_DLL + char const* qpdf_get_info_key(qpdf_data qpdf, char const* key); + + /* Set a value in the info dictionary, possibly replacing an + * existing value. The key must include the leading slash + * (e.g. "/Author"). Passing a null pointer as a value will + * remove the key from the info dictionary. Otherwise, a copy + * will be made of the string that is passed in. + */ + QPDF_DLL + void qpdf_set_info_key(qpdf_data qpdf, char const* key, char const* value); + /* Indicate whether the input file is linearized. */ QPDF_DLL QPDF_BOOL qpdf_is_linearized(qpdf_data qpdf); diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index 10260753..62dba270 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -8,6 +8,7 @@ #include #include #include +#include struct _qpdf_error { @@ -283,6 +284,64 @@ char const* qpdf_get_user_password(qpdf_data qpdf) return qpdf->tmp_string.c_str(); } +char const* qpdf_get_info_key(qpdf_data qpdf, char const* key) +{ + char const* result = 0; + QPDFObjectHandle trailer = qpdf->qpdf->getTrailer(); + if (trailer.hasKey("/Info")) + { + QPDFObjectHandle info = trailer.getKey("/Info"); + if (info.hasKey(key)) + { + QPDFObjectHandle value = info.getKey(key); + if (value.isString()) + { + qpdf->tmp_string = value.getStringValue(); + result = qpdf->tmp_string.c_str(); + } + } + } + QTC::TC("qpdf", "qpdf-c get_info_key", (result == 0 ? 0 : 1)); + return result; +} + +void qpdf_set_info_key(qpdf_data qpdf, char const* key, char const* value) +{ + if ((key == 0) || (std::strlen(key) == 0) || (key[0] != '/')) + { + return; + } + QPDFObjectHandle value_object; + if (value) + { + QTC::TC("qpdf", "qpdf-c set_info_key to value"); + value_object = QPDFObjectHandle::newString(value); + } + else + { + QTC::TC("qpdf", "qpdf-c set_info_key to null"); + value_object = QPDFObjectHandle::newNull(); + } + + QPDFObjectHandle trailer = qpdf->qpdf->getTrailer(); + if (! trailer.hasKey("/Info")) + { + QTC::TC("qpdf", "qpdf-c add info to trailer"); + trailer.replaceKey( + "/Info", + qpdf->qpdf->makeIndirectObject( + QPDFObjectHandle::newDictionary( + std::map()))); + } + else + { + QTC::TC("qpdf", "qpdf-c set-info-key use existing info"); + } + + QPDFObjectHandle info = trailer.getKey("/Info"); + info.replaceOrRemoveKey(key, value_object); +} + QPDF_BOOL qpdf_is_linearized(qpdf_data qpdf) { QTC::TC("qpdf", "qpdf-c called qpdf_is_linearized"); diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index 15130567..0d743677 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -2123,6 +2123,14 @@ print "\n"; objects as vectors. + + + Add functions qpdf_get_info_key and + qpdf_set_info_key to the C API for + manipulating string fields of the document's + /Info dictionary. + + diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c index 0797d9a2..aa61b9a1 100644 --- a/qpdf/qpdf-ctest.c +++ b/qpdf/qpdf-ctest.c @@ -325,6 +325,36 @@ static void test15(char const* infile, report_errors(); } +static void print_info(char const* key) +{ + char const* value = qpdf_get_info_key(qpdf, key); + printf("Info key %s: %s\n", + key, (value ? value : "(null)")); +} + +static void test16(char const* infile, + char const* password, + char const* outfile, + char const* outfile2) +{ + qpdf_read(qpdf, infile, password); + print_info("/Author"); + print_info("/Producer"); + print_info("/Creator"); + qpdf_set_info_key(qpdf, "/Author", "Mr. Potato Head"); + qpdf_set_info_key(qpdf, "/Producer", "QPDF libary"); + qpdf_set_info_key(qpdf, "/Creator", 0); + print_info("/Author"); + print_info("/Producer"); + print_info("/Creator"); + qpdf_init_write(qpdf, outfile); + qpdf_set_static_ID(qpdf, QPDF_TRUE); + qpdf_set_static_aes_IV(qpdf, QPDF_TRUE); + qpdf_set_stream_data_mode(qpdf, qpdf_s_uncompress); + qpdf_write(qpdf); + report_errors(); +} + int main(int argc, char* argv[]) { char* p = 0; @@ -380,6 +410,7 @@ int main(int argc, char* argv[]) (n == 13) ? test13 : (n == 14) ? test14 : (n == 15) ? test15 : + (n == 16) ? test16 : 0); if (fn == 0) diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 8f5d15a3..520b7fbe 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -194,3 +194,8 @@ QPDF stream with CRNL 0 QPDF stream with NL only 0 QPDF replaceObject called with indirect object 0 QPDFWriter copy encrypt metadata 1 +qpdf-c get_info_key 1 +qpdf-c set_info_key to value 0 +qpdf-c set_info_key to null 0 +qpdf-c set-info-key use existing info 0 +qpdf-c add info to trailer 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index c428aec3..b9e14a36 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -111,7 +111,7 @@ $td->runtest("new stream", show_ntests(); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 33; +$n_tests += 37; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -285,6 +285,26 @@ $td->runtest("check output", {$td->FILE => "a.pdf"}, {$td->FILE => "test14-out.pdf"}); +$td->runtest("C API info key functions", + {$td->COMMAND => "qpdf-ctest 16 minimal.pdf '' a.pdf"}, + {$td->FILE => "c-info1.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "c-info-out.pdf"}); +unlink "a.pdf" or die; + +$td->runtest("C API info key functions", + {$td->COMMAND => "qpdf-ctest 16 c-info2-in.pdf '' a.pdf"}, + {$td->FILE => "c-info2.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "c-info-out.pdf"}); +unlink "a.pdf" or die; + show_ntests(); # ---------- $td->notify("--- Error Condition Tests ---"); diff --git a/qpdf/qtest/qpdf/c-info-out.pdf b/qpdf/qtest/qpdf/c-info-out.pdf new file mode 100644 index 00000000..5b4c46cf --- /dev/null +++ b/qpdf/qtest/qpdf/c-info-out.pdf @@ -0,0 +1,44 @@ +%PDF-1.3 +%¿÷¢þ +1 0 obj +<< /Pages 3 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Author (Mr. Potato Head) /Producer (QPDF libary) >> +endobj +3 0 obj +<< /Count 1 /Kids [ 4 0 R ] /Type /Pages >> +endobj +4 0 obj +<< /Contents 5 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 6 0 R >> /ProcSet 7 0 R >> /Type /Page >> +endobj +5 0 obj +<< /Length 44 >> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj +6 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> +endobj +7 0 obj +[ /PDF /Text ] +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000135 00000 n +0000000194 00000 n +0000000337 00000 n +0000000430 00000 n +0000000537 00000 n +trailer << /Info 2 0 R /Root 1 0 R /Size 8 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> +startxref +567 +%%EOF diff --git a/qpdf/qtest/qpdf/c-info1.out b/qpdf/qtest/qpdf/c-info1.out new file mode 100644 index 00000000..8ec9497f --- /dev/null +++ b/qpdf/qtest/qpdf/c-info1.out @@ -0,0 +1,6 @@ +Info key /Author: (null) +Info key /Producer: (null) +Info key /Creator: (null) +Info key /Author: Mr. Potato Head +Info key /Producer: QPDF libary +Info key /Creator: (null) diff --git a/qpdf/qtest/qpdf/c-info2-in.pdf b/qpdf/qtest/qpdf/c-info2-in.pdf new file mode 100644 index 00000000..7a83ea2c Binary files /dev/null and b/qpdf/qtest/qpdf/c-info2-in.pdf differ diff --git a/qpdf/qtest/qpdf/c-info2.out b/qpdf/qtest/qpdf/c-info2.out new file mode 100644 index 00000000..0b25722b --- /dev/null +++ b/qpdf/qtest/qpdf/c-info2.out @@ -0,0 +1,6 @@ +Info key /Author: Someone Else +Info key /Producer: Something Else +Info key /Creator: A. Nony Mous +Info key /Author: Mr. Potato Head +Info key /Producer: QPDF libary +Info key /Creator: (null)