diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index ccc3993e..f540272d 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -365,6 +365,22 @@ class QPDFObjectHandle QPDF_DLL bool isScalar(); + // True if the object is a name object representing the provided name. + QPDF_DLL + bool isNameAndEquals(std::string const& name); + + // True if the object is a dictionary of the specified type and + // subtype, if any. + QPDF_DLL + bool isDictionaryOfType(std::string const& type, + std::string const& subtype = ""); + + // True if the object is a stream of the specified type and + // subtype, if any. + QPDF_DLL + bool isStreamOfType(std::string const& type, + std::string const& subtype = ""); + // Public factory methods // Wrap an object in an array if it is not already an array. This diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index 7369e616..b62f2e09 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -676,6 +676,15 @@ extern "C" { QPDF_BOOL qpdf_oh_is_indirect(qpdf_data qpdf, qpdf_oh oh); QPDF_DLL QPDF_BOOL qpdf_oh_is_scalar(qpdf_data qpdf, qpdf_oh oh); + + QPDF_DLL + QPDF_BOOL qpdf_oh_is_name_and_equals( + qpdf_data qpdf, qpdf_oh oh, char const* name); + + QPDF_DLL + QPDF_BOOL qpdf_oh_is_dictionary_of_type( + qpdf_data qpdf, qpdf_oh oh, char const* type, char const* subtype); + QPDF_DLL enum qpdf_object_type_e qpdf_oh_get_type_code(qpdf_data qpdf, qpdf_oh oh); QPDF_DLL diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 1a8e3223..f2c77dca 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -497,6 +497,34 @@ QPDFObjectHandle::isScalar() isOperator() || isInlineImage())); } +bool +QPDFObjectHandle::isNameAndEquals(std::string const& name) +{ + return isName() && (getName() == name); +} + +bool +QPDFObjectHandle::isDictionaryOfType(std::string const& type, + std::string const& subtype) +{ + if (isDictionary() && getKey("/Type").isNameAndEquals(type)) + { + return (subtype == "") || + (hasKey("/Subtype") && getKey("/Subtype").isNameAndEquals(subtype)); + } + else + { + return false; + } +} + +bool +QPDFObjectHandle::isStreamOfType(std::string const& type, + std::string const& subtype) +{ + return isStream() && getDict().isDictionaryOfType(type, subtype); +} + // Bool accessors bool diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index fb79407d..44950568 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -1148,6 +1148,27 @@ QPDF_BOOL qpdf_oh_is_number(qpdf_data qpdf, qpdf_oh oh) }); } +QPDF_BOOL qpdf_oh_is_name_and_equals( + qpdf_data qpdf, qpdf_oh oh, char const* name) +{ + return do_with_oh( + qpdf, oh, return_false, [name](QPDFObjectHandle& o) { + QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_name_and_equals"); + return o.isNameAndEquals(name); + }); +} + +QPDF_BOOL qpdf_oh_is_dictionary_of_type( + qpdf_data qpdf, qpdf_oh oh, char const* type, char const* subtype) +{ + auto stype = (subtype == nullptr) ? "" : subtype; + return do_with_oh( + qpdf, oh, return_false, [type, stype](QPDFObjectHandle& o) { + QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_dictionary_of_type"); + return o.isDictionaryOfType(type, stype); + }); +} + qpdf_object_type_e qpdf_oh_get_type_code(qpdf_data qpdf, qpdf_oh oh) { return do_with_oh( diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c index af743b01..13144fe3 100644 --- a/qpdf/qpdf-ctest.c +++ b/qpdf/qpdf-ctest.c @@ -694,12 +694,16 @@ static void test25(char const* infile, (strcmp(qpdf_oh_get_utf8_value(qpdf, p_string), "3\xc3\xb7") == 0) && (strcmp(qpdf_oh_unparse_binary(qpdf, p_string), "<33f7>") == 0)); assert(qpdf_oh_get_type_code(qpdf, p_string) == ot_string); + assert(! qpdf_oh_is_name_and_equals(qpdf, p_string, "3\xf7")); assert(strcmp(qpdf_oh_get_type_name(qpdf, p_string), "string") == 0); assert(qpdf_oh_is_dictionary(qpdf, p_dict)); qpdf_oh p_five = qpdf_oh_get_key(qpdf, p_dict, "/Four"); assert(qpdf_oh_is_or_has_name(qpdf, p_five, "/Five")); + assert(! qpdf_oh_is_name_and_equals(qpdf, p_five, "/Five")); assert(qpdf_oh_is_or_has_name( qpdf, qpdf_oh_get_array_item(qpdf, p_five, 0), "/Five")); + assert(qpdf_oh_is_name_and_equals( + qpdf, qpdf_oh_get_array_item(qpdf, p_five, 0), "/Five")); assert(qpdf_oh_is_null(qpdf, p_null)); assert(qpdf_oh_get_type_code(qpdf, p_null) == ot_null); assert(strcmp(qpdf_oh_get_type_name(qpdf, p_null), "null") == 0); @@ -710,10 +714,17 @@ static void test25(char const* infile, qpdf_oh_erase_item(qpdf, parsed, 4); qpdf_oh_insert_item( qpdf, parsed, 2, - qpdf_oh_parse(qpdf, "<>")); + qpdf_oh_parse( + qpdf, "<>")); qpdf_oh new_dict = qpdf_oh_get_array_item(qpdf, parsed, 2); assert(qpdf_oh_has_key(qpdf, new_dict, "/A")); assert(qpdf_oh_has_key(qpdf, new_dict, "/D")); + assert(qpdf_oh_is_dictionary_of_type(qpdf, new_dict, "/Test", "")); + assert(qpdf_oh_is_dictionary_of_type(qpdf, new_dict, "/Test", 0)); + assert(qpdf_oh_is_dictionary_of_type(qpdf, new_dict, "/Test", "/Marvin")); + assert(! qpdf_oh_is_dictionary_of_type(qpdf, new_dict, "/Test2", "")); + assert(! qpdf_oh_is_dictionary_of_type(qpdf, new_dict, "/Test", "/M")); + assert(! qpdf_oh_is_dictionary_of_type(qpdf, new_dict, "", "")); qpdf_oh new_array = qpdf_oh_new_array(qpdf); qpdf_oh_replace_or_remove_key( qpdf, new_dict, "/A", qpdf_oh_new_null(qpdf)); diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 35417d4c..d900b3d6 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -474,6 +474,8 @@ qpdf-c called qpdf_oh_is_dictionary 0 qpdf-c called qpdf_oh_is_stream 0 qpdf-c called qpdf_oh_is_indirect 0 qpdf-c called qpdf_oh_is_scalar 0 +qpdf-c called qpdf_oh_is_name_and_equals 0 +qpdf-c called qpdf_oh_is_dictionary_of_type 0 qpdf-c called qpdf_oh_get_type_code 0 qpdf-c called qpdf_oh_get_type_name 0 qpdf-c array to wrap_in_array 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 5f61388b..7dce31ed 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -1913,7 +1913,7 @@ $td->runtest("progress report on small file", show_ntests(); # ---------- $td->notify("--- Type checks ---"); -$n_tests += 4; +$n_tests += 5; # Whenever object-types.pdf is edited, object-types-os.pdf should be # regenerated. $td->runtest("ensure object-types-os is up-to-date", @@ -1937,6 +1937,10 @@ $td->runtest("type checks with object streams", {$td->FILE => "object-types-os.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +$td->runtest("compound type checks", + {$td->COMMAND => "test_driver 82 object-types-os.pdf"}, + {$td->STRING => "test 82 done\n", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); # ---------- $td->notify("--- Coalesce contents ---"); diff --git a/qpdf/qtest/qpdf/c-object-handle-creation-out.pdf b/qpdf/qtest/qpdf/c-object-handle-creation-out.pdf index 400ebf0d..729feb0d 100644 --- a/qpdf/qtest/qpdf/c-object-handle-creation-out.pdf +++ b/qpdf/qtest/qpdf/c-object-handle-creation-out.pdf @@ -18,6 +18,8 @@ ] /C << >> + /Subtype /Marvin + /Type /Test >> /Type /Catalog >> @@ -93,17 +95,17 @@ xref 0 8 0000000000 65535 f 0000000025 00000 n -0000000240 00000 n -0000000322 00000 n -0000000537 00000 n -0000000636 00000 n -0000000655 00000 n -0000000773 00000 n +0000000277 00000 n +0000000359 00000 n +0000000574 00000 n +0000000673 00000 n +0000000692 00000 n +0000000810 00000 n trailer << /Root 1 0 R /Size 8 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> startxref -808 +845 %%EOF diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index f01c31db..802948cc 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -3097,6 +3097,39 @@ static void test_81(QPDF& pdf, char const* arg2) } } +static void test_82(QPDF& pdf, char const* arg2) +{ + // Exercise compound test methods QPDFObjectHandle::isNameAndEquals, + // isDictionaryOfType and isStreamOfType + auto name = QPDFObjectHandle::newName("/Marvin"); + auto str = QPDFObjectHandle::newString("/Marvin"); + assert(name.isNameAndEquals("/Marvin")); + assert(! name.isNameAndEquals("Marvin")); + assert(! str.isNameAndEquals("/Marvin")); + auto dict = QPDFObjectHandle::parse("<>"); + assert(dict.isDictionaryOfType( "/Test", "")); + assert(dict.isDictionaryOfType("/Test")); + assert(dict.isDictionaryOfType("/Test", "/Marvin")); + assert(! dict.isDictionaryOfType("/Test2", "")); + assert(! dict.isDictionaryOfType("/Test2", "/Marvin")); + assert(! dict.isDictionaryOfType("/Test", "/M")); + assert(! dict.isDictionaryOfType("", "/Marvin")); + assert(! dict.isDictionaryOfType("", "")); + dict = QPDFObjectHandle::parse("<>"); + assert(! dict.isDictionaryOfType("/Test")); + dict = QPDFObjectHandle::parse("<>"); + assert(! dict.isDictionaryOfType("Test")); + dict = QPDFObjectHandle::parse("<>"); + assert(! dict.isDictionaryOfType("Test")); + dict = QPDFObjectHandle::parse("<>"); + assert(! dict.isDictionaryOfType("/Test", "Marvin")); + auto stream = pdf.getObjectByID(1,0); + assert(stream.isStreamOfType("/ObjStm")); + assert(! stream.isStreamOfType("/Test")); + assert(! pdf.getObjectByID(2,0).isStreamOfType("/Pages")); +} + + void runtest(int n, char const* filename1, char const* arg2) { // Most tests here are crafted to work on specific files. Look at @@ -3210,7 +3243,7 @@ void runtest(int n, char const* filename1, char const* arg2) {68, test_68}, {69, test_69}, {70, test_70}, {71, test_71}, {72, test_72}, {73, test_73}, {74, test_74}, {75, test_75}, {76, test_76}, {77, test_77}, {78, test_78}, {79, test_79}, - {80, test_80}, {81, test_81}, + {80, test_80}, {81, test_81}, {82, test_82}, }; auto fn = test_functions.find(n);