Commit 1c62c2a3427e92846ddaaae44f864022b2aade4f
1 parent
8e0b1533
C API: expose functions for indirect objects (fixes #588)
Showing
8 changed files
with
213 additions
and
1 deletions
ChangeLog
| 1 | 2021-12-10 Jay Berkenbilt <ejb@ql.org> | 1 | 2021-12-10 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * C API: add qpdf_get_object_by_id, qpdf_make_indirect_object, and | ||
| 4 | + qpdf_replace_object, exposing the corresponding methods in QPDF | ||
| 5 | + and QPDFObjectHandle. Fixes #588. | ||
| 6 | + | ||
| 3 | * Add missing QPDF_DLL to QPDFObjectHandle::addTokenFilter so that | 7 | * Add missing QPDF_DLL to QPDFObjectHandle::addTokenFilter so that |
| 4 | it is actually accessible as part of the public interface as | 8 | it is actually accessible as part of the public interface as |
| 5 | intended. Fixes #580. | 9 | intended. Fixes #580. |
include/qpdf/qpdf-c.h
| @@ -618,6 +618,15 @@ extern "C" { | @@ -618,6 +618,15 @@ extern "C" { | ||
| 618 | QPDF_DLL | 618 | QPDF_DLL |
| 619 | qpdf_oh qpdf_get_root(qpdf_data data); | 619 | qpdf_oh qpdf_get_root(qpdf_data data); |
| 620 | 620 | ||
| 621 | + /* Retrieve and replace indirect objects */ | ||
| 622 | + QPDF_DLL | ||
| 623 | + qpdf_oh qpdf_get_object_by_id(qpdf_data qpdf, int objid, int generation); | ||
| 624 | + QPDF_DLL | ||
| 625 | + qpdf_oh qpdf_make_indirect_object(qpdf_data qpdf, qpdf_oh oh); | ||
| 626 | + QPDF_DLL | ||
| 627 | + void qpdf_replace_object( | ||
| 628 | + qpdf_data qpdf, int objid, int generation, qpdf_oh oh); | ||
| 629 | + | ||
| 621 | /* Wrappers around QPDFObjectHandle methods. Be sure to read | 630 | /* Wrappers around QPDFObjectHandle methods. Be sure to read |
| 622 | * corresponding comments in QPDFObjectHandle.hh to understand | 631 | * corresponding comments in QPDFObjectHandle.hh to understand |
| 623 | * what each function does and what kinds of objects it applies | 632 | * what each function does and what kinds of objects it applies |
libqpdf/qpdf-c.cc
| @@ -972,6 +972,12 @@ qpdf_oh qpdf_get_root(qpdf_data qpdf) | @@ -972,6 +972,12 @@ qpdf_oh qpdf_get_root(qpdf_data qpdf) | ||
| 972 | }); | 972 | }); |
| 973 | } | 973 | } |
| 974 | 974 | ||
| 975 | +qpdf_oh qpdf_get_object_by_id(qpdf_data qpdf, int objid, int generation) | ||
| 976 | +{ | ||
| 977 | + QTC::TC("qpdf", "qpdf-c called qpdf_get_object_by_id"); | ||
| 978 | + return new_object(qpdf, qpdf->qpdf->getObjectByID(objid, generation)); | ||
| 979 | +} | ||
| 980 | + | ||
| 975 | template<class RET> | 981 | template<class RET> |
| 976 | static RET do_with_oh( | 982 | static RET do_with_oh( |
| 977 | qpdf_data qpdf, qpdf_oh oh, | 983 | qpdf_data qpdf, qpdf_oh oh, |
| @@ -1008,6 +1014,15 @@ static void do_with_oh_void( | @@ -1008,6 +1014,15 @@ static void do_with_oh_void( | ||
| 1008 | }); | 1014 | }); |
| 1009 | } | 1015 | } |
| 1010 | 1016 | ||
| 1017 | +void qpdf_replace_object(qpdf_data qpdf, int objid, int generation, qpdf_oh oh) | ||
| 1018 | +{ | ||
| 1019 | + do_with_oh_void( | ||
| 1020 | + qpdf, oh, [&qpdf, &objid, &generation](QPDFObjectHandle& o) { | ||
| 1021 | + QTC::TC("qpdf", "qpdf-c called qpdf_replace_object"); | ||
| 1022 | + qpdf->qpdf->replaceObject(objid, generation, o); | ||
| 1023 | + }); | ||
| 1024 | +} | ||
| 1025 | + | ||
| 1011 | QPDF_BOOL qpdf_oh_is_initialized(qpdf_data qpdf, qpdf_oh oh) | 1026 | QPDF_BOOL qpdf_oh_is_initialized(qpdf_data qpdf, qpdf_oh oh) |
| 1012 | { | 1027 | { |
| 1013 | QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_initialized"); | 1028 | 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) | @@ -1421,6 +1436,16 @@ void qpdf_oh_make_direct(qpdf_data qpdf, qpdf_oh oh) | ||
| 1421 | }); | 1436 | }); |
| 1422 | } | 1437 | } |
| 1423 | 1438 | ||
| 1439 | +qpdf_oh qpdf_make_indirect_object(qpdf_data qpdf, qpdf_oh oh) | ||
| 1440 | +{ | ||
| 1441 | + return do_with_oh<qpdf_oh>( | ||
| 1442 | + qpdf, oh, | ||
| 1443 | + return_uninitialized(qpdf), | ||
| 1444 | + [&qpdf](QPDFObjectHandle& o) { | ||
| 1445 | + return new_object(qpdf, qpdf->qpdf->makeIndirectObject(o)); | ||
| 1446 | + }); | ||
| 1447 | +} | ||
| 1448 | + | ||
| 1424 | static QPDFObjectHandle | 1449 | static QPDFObjectHandle |
| 1425 | qpdf_oh_item_internal(qpdf_data qpdf, qpdf_oh item) | 1450 | qpdf_oh_item_internal(qpdf_data qpdf, qpdf_oh item) |
| 1426 | { | 1451 | { |
manual/qpdf-manual.xml
| @@ -5263,6 +5263,15 @@ print "\n"; | @@ -5263,6 +5263,15 @@ print "\n"; | ||
| 5263 | C API. This allows you to clone an object handle. | 5263 | C API. This allows you to clone an object handle. |
| 5264 | </para> | 5264 | </para> |
| 5265 | </listitem> | 5265 | </listitem> |
| 5266 | + <listitem> | ||
| 5267 | + <para> | ||
| 5268 | + Add <function>qpdf_get_object_by_id</function>, | ||
| 5269 | + <function>qpdf_make_indirect_object</function>, and | ||
| 5270 | + <function>qpdf_replace_object</function>, exposing the | ||
| 5271 | + corresponding methods in <classname>QPDF</classname> and | ||
| 5272 | + <classname>QPDFObjectHandle</classname>. | ||
| 5273 | + </para> | ||
| 5274 | + </listitem> | ||
| 5266 | </itemizedlist> | 5275 | </itemizedlist> |
| 5267 | </listitem> | 5276 | </listitem> |
| 5268 | </itemizedlist> | 5277 | </itemizedlist> |
qpdf/qpdf-ctest.c
| @@ -881,6 +881,56 @@ static void test31(char const* infile, | @@ -881,6 +881,56 @@ static void test31(char const* infile, | ||
| 881 | report_errors(); | 881 | report_errors(); |
| 882 | } | 882 | } |
| 883 | 883 | ||
| 884 | +static void test32(char const* infile, | ||
| 885 | + char const* password, | ||
| 886 | + char const* outfile, | ||
| 887 | + char const* outfile2) | ||
| 888 | +{ | ||
| 889 | + /* This test case is designed for minimal.pdf. */ | ||
| 890 | + assert(qpdf_read(qpdf, infile, password) == 0); | ||
| 891 | + qpdf_oh page = qpdf_get_object_by_id(qpdf, 3, 0); | ||
| 892 | + assert(qpdf_oh_is_dictionary(qpdf, page)); | ||
| 893 | + assert(qpdf_oh_has_key(qpdf, page, "/MediaBox")); | ||
| 894 | + report_errors(); | ||
| 895 | +} | ||
| 896 | + | ||
| 897 | +static void test33(char const* infile, | ||
| 898 | + char const* password, | ||
| 899 | + char const* outfile, | ||
| 900 | + char const* outfile2) | ||
| 901 | +{ | ||
| 902 | + /* This test case is designed for minimal.pdf. */ | ||
| 903 | + | ||
| 904 | + /* Convert a direct object to an indirect object and replace it. */ | ||
| 905 | + assert(qpdf_read(qpdf, infile, password) == 0); | ||
| 906 | + qpdf_oh root = qpdf_get_root(qpdf); | ||
| 907 | + qpdf_oh pages = qpdf_oh_get_key(qpdf, root, "/Pages"); | ||
| 908 | + qpdf_oh kids = qpdf_oh_get_key(qpdf, pages, "/Kids"); | ||
| 909 | + qpdf_oh page1 = qpdf_oh_get_array_item(qpdf, kids, 0); | ||
| 910 | + qpdf_oh mediabox = qpdf_oh_get_key(qpdf, page1, "/MediaBox"); | ||
| 911 | + assert(! qpdf_oh_is_indirect(qpdf, mediabox)); | ||
| 912 | + qpdf_oh i_mediabox = qpdf_make_indirect_object(qpdf, mediabox); | ||
| 913 | + assert(qpdf_oh_is_indirect(qpdf, i_mediabox)); | ||
| 914 | + qpdf_oh_replace_key(qpdf, page1, "/MediaBox", i_mediabox); | ||
| 915 | + | ||
| 916 | + /* Replace a different indirect object */ | ||
| 917 | + qpdf_oh resources = qpdf_oh_get_key(qpdf, page1, "/Resources"); | ||
| 918 | + qpdf_oh procset = qpdf_oh_get_key(qpdf, resources, "/ProcSet"); | ||
| 919 | + assert(qpdf_oh_is_indirect(qpdf, procset)); | ||
| 920 | + qpdf_replace_object( | ||
| 921 | + qpdf, | ||
| 922 | + qpdf_oh_get_object_id(qpdf, procset), | ||
| 923 | + qpdf_oh_get_generation(qpdf, procset), | ||
| 924 | + qpdf_oh_parse(qpdf, "[/PDF]")); | ||
| 925 | + | ||
| 926 | + qpdf_init_write(qpdf, outfile); | ||
| 927 | + qpdf_set_static_ID(qpdf, QPDF_TRUE); | ||
| 928 | + qpdf_set_qdf_mode(qpdf, QPDF_TRUE); | ||
| 929 | + qpdf_set_suppress_original_object_IDs(qpdf, QPDF_TRUE); | ||
| 930 | + qpdf_write(qpdf); | ||
| 931 | + report_errors(); | ||
| 932 | +} | ||
| 933 | + | ||
| 884 | int main(int argc, char* argv[]) | 934 | int main(int argc, char* argv[]) |
| 885 | { | 935 | { |
| 886 | char* p = 0; | 936 | char* p = 0; |
| @@ -952,6 +1002,8 @@ int main(int argc, char* argv[]) | @@ -952,6 +1002,8 @@ int main(int argc, char* argv[]) | ||
| 952 | (n == 29) ? test29 : | 1002 | (n == 29) ? test29 : |
| 953 | (n == 30) ? test30 : | 1003 | (n == 30) ? test30 : |
| 954 | (n == 31) ? test31 : | 1004 | (n == 31) ? test31 : |
| 1005 | + (n == 32) ? test32 : | ||
| 1006 | + (n == 33) ? test33 : | ||
| 955 | 0); | 1007 | 0); |
| 956 | 1008 | ||
| 957 | if (fn == 0) | 1009 | if (fn == 0) |
qpdf/qpdf.testcov
| @@ -606,3 +606,5 @@ qpdf-c called qpdf_oh_new_uninitialized 0 | @@ -606,3 +606,5 @@ qpdf-c called qpdf_oh_new_uninitialized 0 | ||
| 606 | qpdf-c warn about oh error 1 | 606 | qpdf-c warn about oh error 1 |
| 607 | qpdf-c registered oh error handler 0 | 607 | qpdf-c registered oh error handler 0 |
| 608 | qpdf-c cleanup warned about unhandled error 0 | 608 | qpdf-c cleanup warned about unhandled error 0 |
| 609 | +qpdf-c called qpdf_get_object_by_id 0 | ||
| 610 | +qpdf-c called qpdf_replace_object 0 |
qpdf/qtest/qpdf.test
| @@ -4812,7 +4812,7 @@ foreach my $i (@c_check_types) | @@ -4812,7 +4812,7 @@ foreach my $i (@c_check_types) | ||
| 4812 | show_ntests(); | 4812 | show_ntests(); |
| 4813 | # ---------- | 4813 | # ---------- |
| 4814 | $td->notify("--- C API Object Handle ---"); | 4814 | $td->notify("--- C API Object Handle ---"); |
| 4815 | -$n_tests += 10; | 4815 | +$n_tests += 13; |
| 4816 | 4816 | ||
| 4817 | $td->runtest("C check object handles", | 4817 | $td->runtest("C check object handles", |
| 4818 | {$td->COMMAND => "qpdf-ctest 24 minimal.pdf '' a.pdf"}, | 4818 | {$td->COMMAND => "qpdf-ctest 24 minimal.pdf '' a.pdf"}, |
| @@ -4831,6 +4831,14 @@ $td->runtest("check output", | @@ -4831,6 +4831,14 @@ $td->runtest("check output", | ||
| 4831 | {$td->FILE => 'a.pdf'}, | 4831 | {$td->FILE => 'a.pdf'}, |
| 4832 | {$td->FILE => 'c-object-handle-creation-out.pdf'}); | 4832 | {$td->FILE => 'c-object-handle-creation-out.pdf'}); |
| 4833 | 4833 | ||
| 4834 | +$td->runtest("C indirect objects", | ||
| 4835 | + {$td->COMMAND => "qpdf-ctest 33 minimal.pdf '' a.pdf"}, | ||
| 4836 | + {$td->STRING => "", $td->EXIT_STATUS => 0}, | ||
| 4837 | + $td->NORMALIZE_NEWLINES); | ||
| 4838 | +$td->runtest("check output", | ||
| 4839 | + {$td->FILE => 'a.pdf'}, | ||
| 4840 | + {$td->FILE => 'c-indirect-objects-out.pdf'}); | ||
| 4841 | + | ||
| 4834 | $td->runtest("C uninitialized objects", | 4842 | $td->runtest("C uninitialized objects", |
| 4835 | {$td->COMMAND => "qpdf-ctest 26 '' '' ''"}, | 4843 | {$td->COMMAND => "qpdf-ctest 26 '' '' ''"}, |
| 4836 | {$td->STRING => "", $td->EXIT_STATUS => 0}, | 4844 | {$td->STRING => "", $td->EXIT_STATUS => 0}, |
| @@ -4855,6 +4863,10 @@ $td->runtest("C type mismatch warning", | @@ -4855,6 +4863,10 @@ $td->runtest("C type mismatch warning", | ||
| 4855 | {$td->COMMAND => "qpdf-ctest 31 minimal.pdf '' ''"}, | 4863 | {$td->COMMAND => "qpdf-ctest 31 minimal.pdf '' ''"}, |
| 4856 | {$td->FILE => "c-type-warning.out", $td->EXIT_STATUS => 0}, | 4864 | {$td->FILE => "c-type-warning.out", $td->EXIT_STATUS => 0}, |
| 4857 | $td->NORMALIZE_NEWLINES); | 4865 | $td->NORMALIZE_NEWLINES); |
| 4866 | +$td->runtest("C get object by ID", | ||
| 4867 | + {$td->COMMAND => "qpdf-ctest 32 minimal.pdf '' ''"}, | ||
| 4868 | + {$td->STRING => "", $td->EXIT_STATUS => 0}, | ||
| 4869 | + $td->NORMALIZE_NEWLINES); | ||
| 4858 | 4870 | ||
| 4859 | show_ntests(); | 4871 | show_ntests(); |
| 4860 | # ---------- | 4872 | # ---------- |
qpdf/qtest/qpdf/c-indirect-objects-out.pdf
0 โ 100644
| 1 | +%PDF-1.3 | ||
| 2 | +%ยฟรทยขรพ | ||
| 3 | +%QDF-1.0 | ||
| 4 | + | ||
| 5 | +1 0 obj | ||
| 6 | +<< | ||
| 7 | + /Pages 2 0 R | ||
| 8 | + /Type /Catalog | ||
| 9 | +>> | ||
| 10 | +endobj | ||
| 11 | + | ||
| 12 | +2 0 obj | ||
| 13 | +<< | ||
| 14 | + /Count 1 | ||
| 15 | + /Kids [ | ||
| 16 | + 3 0 R | ||
| 17 | + ] | ||
| 18 | + /Type /Pages | ||
| 19 | +>> | ||
| 20 | +endobj | ||
| 21 | + | ||
| 22 | +%% Page 1 | ||
| 23 | +3 0 obj | ||
| 24 | +<< | ||
| 25 | + /Contents 4 0 R | ||
| 26 | + /MediaBox 6 0 R | ||
| 27 | + /Parent 2 0 R | ||
| 28 | + /Resources << | ||
| 29 | + /Font << | ||
| 30 | + /F1 7 0 R | ||
| 31 | + >> | ||
| 32 | + /ProcSet 8 0 R | ||
| 33 | + >> | ||
| 34 | + /Type /Page | ||
| 35 | +>> | ||
| 36 | +endobj | ||
| 37 | + | ||
| 38 | +%% Contents for page 1 | ||
| 39 | +4 0 obj | ||
| 40 | +<< | ||
| 41 | + /Length 5 0 R | ||
| 42 | +>> | ||
| 43 | +stream | ||
| 44 | +BT | ||
| 45 | + /F1 24 Tf | ||
| 46 | + 72 720 Td | ||
| 47 | + (Potato) Tj | ||
| 48 | +ET | ||
| 49 | +endstream | ||
| 50 | +endobj | ||
| 51 | + | ||
| 52 | +5 0 obj | ||
| 53 | +44 | ||
| 54 | +endobj | ||
| 55 | + | ||
| 56 | +6 0 obj | ||
| 57 | +[ | ||
| 58 | + 0 | ||
| 59 | + 0 | ||
| 60 | + 612 | ||
| 61 | + 792 | ||
| 62 | +] | ||
| 63 | +endobj | ||
| 64 | + | ||
| 65 | +7 0 obj | ||
| 66 | +<< | ||
| 67 | + /BaseFont /Helvetica | ||
| 68 | + /Encoding /WinAnsiEncoding | ||
| 69 | + /Name /F1 | ||
| 70 | + /Subtype /Type1 | ||
| 71 | + /Type /Font | ||
| 72 | +>> | ||
| 73 | +endobj | ||
| 74 | + | ||
| 75 | +8 0 obj | ||
| 76 | +[ | ||
| 77 | |||
| 78 | +] | ||
| 79 | +endobj | ||
| 80 | + | ||
| 81 | +xref | ||
| 82 | +0 9 | ||
| 83 | +0000000000 65535 f | ||
| 84 | +0000000025 00000 n | ||
| 85 | +0000000079 00000 n | ||
| 86 | +0000000161 00000 n | ||
| 87 | +0000000348 00000 n | ||
| 88 | +0000000447 00000 n | ||
| 89 | +0000000466 00000 n | ||
| 90 | +0000000506 00000 n | ||
| 91 | +0000000624 00000 n | ||
| 92 | +trailer << | ||
| 93 | + /Root 1 0 R | ||
| 94 | + /Size 9 | ||
| 95 | + /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] | ||
| 96 | +>> | ||
| 97 | +startxref | ||
| 98 | +651 | ||
| 99 | +%%EOF |