From 1c62c2a3427e92846ddaaae44f864022b2aade4f Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 10 Dec 2021 14:57:11 -0500 Subject: [PATCH] C API: expose functions for indirect objects (fixes #588) --- ChangeLog | 4 + include/qpdf/qpdf-c.h | 9 ++ libqpdf/qpdf-c.cc | 25 ++++++ manual/qpdf-manual.xml | 9 ++ qpdf/qpdf-ctest.c | 52 ++++++++++++ qpdf/qpdf.testcov | 2 + qpdf/qtest/qpdf.test | 14 ++- qpdf/qtest/qpdf/c-indirect-objects-out.pdf | 99 ++++++++++++++++++++++ 8 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 qpdf/qtest/qpdf/c-indirect-objects-out.pdf diff --git a/ChangeLog b/ChangeLog index 9efbdc15..f100817f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2021-12-10 Jay Berkenbilt + * C API: add qpdf_get_object_by_id, qpdf_make_indirect_object, and + qpdf_replace_object, exposing the corresponding methods in QPDF + and QPDFObjectHandle. Fixes #588. + * Add missing QPDF_DLL to QPDFObjectHandle::addTokenFilter so that it is actually accessible as part of the public interface as intended. Fixes #580. diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index 90219123..fc393152 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -618,6 +618,15 @@ extern "C" { QPDF_DLL qpdf_oh qpdf_get_root(qpdf_data data); + /* Retrieve and replace indirect objects */ + QPDF_DLL + qpdf_oh qpdf_get_object_by_id(qpdf_data qpdf, int objid, int generation); + QPDF_DLL + qpdf_oh qpdf_make_indirect_object(qpdf_data qpdf, qpdf_oh oh); + QPDF_DLL + void qpdf_replace_object( + qpdf_data qpdf, int objid, int generation, qpdf_oh oh); + /* Wrappers around QPDFObjectHandle methods. Be sure to read * corresponding comments in QPDFObjectHandle.hh to understand * what each function does and what kinds of objects it applies diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index b7e67e85..1183ffd2 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -972,6 +972,12 @@ qpdf_oh qpdf_get_root(qpdf_data qpdf) }); } +qpdf_oh qpdf_get_object_by_id(qpdf_data qpdf, int objid, int generation) +{ + QTC::TC("qpdf", "qpdf-c called qpdf_get_object_by_id"); + return new_object(qpdf, qpdf->qpdf->getObjectByID(objid, generation)); +} + template static RET do_with_oh( qpdf_data qpdf, qpdf_oh oh, @@ -1008,6 +1014,15 @@ static void do_with_oh_void( }); } +void qpdf_replace_object(qpdf_data qpdf, int objid, int generation, qpdf_oh oh) +{ + do_with_oh_void( + qpdf, oh, [&qpdf, &objid, &generation](QPDFObjectHandle& o) { + QTC::TC("qpdf", "qpdf-c called qpdf_replace_object"); + qpdf->qpdf->replaceObject(objid, generation, o); + }); +} + QPDF_BOOL qpdf_oh_is_initialized(qpdf_data qpdf, qpdf_oh oh) { QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_initialized"); @@ -1421,6 +1436,16 @@ void qpdf_oh_make_direct(qpdf_data qpdf, qpdf_oh oh) }); } +qpdf_oh qpdf_make_indirect_object(qpdf_data qpdf, qpdf_oh oh) +{ + return do_with_oh( + qpdf, oh, + return_uninitialized(qpdf), + [&qpdf](QPDFObjectHandle& o) { + return new_object(qpdf, qpdf->qpdf->makeIndirectObject(o)); + }); +} + static QPDFObjectHandle qpdf_oh_item_internal(qpdf_data qpdf, qpdf_oh item) { diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index f7d224e5..42b1d25c 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -5263,6 +5263,15 @@ print "\n"; C API. This allows you to clone an object handle. + + + Add qpdf_get_object_by_id, + qpdf_make_indirect_object, and + qpdf_replace_object, exposing the + corresponding methods in QPDF and + QPDFObjectHandle. + + diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c index 6ee19004..d789aa61 100644 --- a/qpdf/qpdf-ctest.c +++ b/qpdf/qpdf-ctest.c @@ -881,6 +881,56 @@ static void test31(char const* infile, report_errors(); } +static void test32(char const* infile, + char const* password, + char const* outfile, + char const* outfile2) +{ + /* This test case is designed for minimal.pdf. */ + assert(qpdf_read(qpdf, infile, password) == 0); + qpdf_oh page = qpdf_get_object_by_id(qpdf, 3, 0); + assert(qpdf_oh_is_dictionary(qpdf, page)); + assert(qpdf_oh_has_key(qpdf, page, "/MediaBox")); + report_errors(); +} + +static void test33(char const* infile, + char const* password, + char const* outfile, + char const* outfile2) +{ + /* This test case is designed for minimal.pdf. */ + + /* Convert a direct object to an indirect object and replace it. */ + assert(qpdf_read(qpdf, infile, password) == 0); + qpdf_oh root = qpdf_get_root(qpdf); + qpdf_oh pages = qpdf_oh_get_key(qpdf, root, "/Pages"); + qpdf_oh kids = qpdf_oh_get_key(qpdf, pages, "/Kids"); + qpdf_oh page1 = qpdf_oh_get_array_item(qpdf, kids, 0); + qpdf_oh mediabox = qpdf_oh_get_key(qpdf, page1, "/MediaBox"); + assert(! qpdf_oh_is_indirect(qpdf, mediabox)); + qpdf_oh i_mediabox = qpdf_make_indirect_object(qpdf, mediabox); + assert(qpdf_oh_is_indirect(qpdf, i_mediabox)); + qpdf_oh_replace_key(qpdf, page1, "/MediaBox", i_mediabox); + + /* Replace a different indirect object */ + qpdf_oh resources = qpdf_oh_get_key(qpdf, page1, "/Resources"); + qpdf_oh procset = qpdf_oh_get_key(qpdf, resources, "/ProcSet"); + assert(qpdf_oh_is_indirect(qpdf, procset)); + qpdf_replace_object( + qpdf, + qpdf_oh_get_object_id(qpdf, procset), + qpdf_oh_get_generation(qpdf, procset), + qpdf_oh_parse(qpdf, "[/PDF]")); + + qpdf_init_write(qpdf, outfile); + qpdf_set_static_ID(qpdf, QPDF_TRUE); + qpdf_set_qdf_mode(qpdf, QPDF_TRUE); + qpdf_set_suppress_original_object_IDs(qpdf, QPDF_TRUE); + qpdf_write(qpdf); + report_errors(); +} + int main(int argc, char* argv[]) { char* p = 0; @@ -952,6 +1002,8 @@ int main(int argc, char* argv[]) (n == 29) ? test29 : (n == 30) ? test30 : (n == 31) ? test31 : + (n == 32) ? test32 : + (n == 33) ? test33 : 0); if (fn == 0) diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 67311b01..9b4e3cea 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -606,3 +606,5 @@ qpdf-c called qpdf_oh_new_uninitialized 0 qpdf-c warn about oh error 1 qpdf-c registered oh error handler 0 qpdf-c cleanup warned about unhandled error 0 +qpdf-c called qpdf_get_object_by_id 0 +qpdf-c called qpdf_replace_object 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 384d9dca..c7881e02 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -4812,7 +4812,7 @@ foreach my $i (@c_check_types) show_ntests(); # ---------- $td->notify("--- C API Object Handle ---"); -$n_tests += 10; +$n_tests += 13; $td->runtest("C check object handles", {$td->COMMAND => "qpdf-ctest 24 minimal.pdf '' a.pdf"}, @@ -4831,6 +4831,14 @@ $td->runtest("check output", {$td->FILE => 'a.pdf'}, {$td->FILE => 'c-object-handle-creation-out.pdf'}); +$td->runtest("C indirect objects", + {$td->COMMAND => "qpdf-ctest 33 minimal.pdf '' a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => 'a.pdf'}, + {$td->FILE => 'c-indirect-objects-out.pdf'}); + $td->runtest("C uninitialized objects", {$td->COMMAND => "qpdf-ctest 26 '' '' ''"}, {$td->STRING => "", $td->EXIT_STATUS => 0}, @@ -4855,6 +4863,10 @@ $td->runtest("C type mismatch warning", {$td->COMMAND => "qpdf-ctest 31 minimal.pdf '' ''"}, {$td->FILE => "c-type-warning.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +$td->runtest("C get object by ID", + {$td->COMMAND => "qpdf-ctest 32 minimal.pdf '' ''"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); show_ntests(); # ---------- diff --git a/qpdf/qtest/qpdf/c-indirect-objects-out.pdf b/qpdf/qtest/qpdf/c-indirect-objects-out.pdf new file mode 100644 index 00000000..fe6dc178 --- /dev/null +++ b/qpdf/qtest/qpdf/c-indirect-objects-out.pdf @@ -0,0 +1,99 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj + +2 0 obj +<< + /Count 1 + /Kids [ + 3 0 R + ] + /Type /Pages +>> +endobj + +%% Page 1 +3 0 obj +<< + /Contents 4 0 R + /MediaBox 6 0 R + /Parent 2 0 R + /Resources << + /Font << + /F1 7 0 R + >> + /ProcSet 8 0 R + >> + /Type /Page +>> +endobj + +%% Contents for page 1 +4 0 obj +<< + /Length 5 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +5 0 obj +44 +endobj + +6 0 obj +[ + 0 + 0 + 612 + 792 +] +endobj + +7 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +8 0 obj +[ + /PDF +] +endobj + +xref +0 9 +0000000000 65535 f +0000000025 00000 n +0000000079 00000 n +0000000161 00000 n +0000000348 00000 n +0000000447 00000 n +0000000466 00000 n +0000000506 00000 n +0000000624 00000 n +trailer << + /Root 1 0 R + /Size 9 + /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] +>> +startxref +651 +%%EOF