From 5cf4090aee4a269186e13d902f91e6af3411f4a6 Mon Sep 17 00:00:00 2001 From: Masamichi Hosoda Date: Tue, 1 Oct 2019 23:14:59 +0900 Subject: [PATCH] Add QPDFWriter::getRenumberedObjGen() --- include/qpdf/QPDFWriter.hh | 5 + libqpdf/QPDFWriter.cc | 6 + qpdf/build.mk | 1 + qpdf/qtest/qpdf.test | 59 ++++++++ qpdf/test_renumber.cc | 273 +++++++++++++++++++++++++++++++++++++ 5 files changed, 344 insertions(+) create mode 100644 qpdf/test_renumber.cc diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index edea3c15..eebc84c3 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -465,6 +465,11 @@ class QPDFWriter QPDF_DLL void write(); + // Return renumbered ObjGen that was written into the final file. + // This method can be used after calling write(). + QPDF_DLL + QPDFObjGen getRenumberedObjGen(QPDFObjGen); + private: // flags used by unparseObject static int const f_stream = 1 << 0; diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 4397c3ad..247a1ac5 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -2741,6 +2741,12 @@ QPDFWriter::write() indicateProgress(false, true); } +QPDFObjGen +QPDFWriter::getRenumberedObjGen(QPDFObjGen og) +{ + return QPDFObjGen(this->m->obj_renumber[og], 0); +} + void QPDFWriter::enqueuePart(std::vector& part) { diff --git a/qpdf/build.mk b/qpdf/build.mk index 204dbd3c..5b828473 100644 --- a/qpdf/build.mk +++ b/qpdf/build.mk @@ -5,6 +5,7 @@ BINS_qpdf = \ test_large_file \ test_pdf_doc_encoding \ test_pdf_unicode \ + test_renumber \ test_tokenizer \ test_unicode_filenames \ test_xref diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index c4ddce9e..8dd7a4bf 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -4034,6 +4034,65 @@ $td->runtest("with object streams", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +show_ntests(); +# ---------- +$td->notify("--- Renumber Objects / XRef ---"); +$n_tests += 8; + +$td->runtest("w/o objstm", + {$td->COMMAND => "test_renumber minimal.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/ objstm", + {$td->COMMAND => "test_renumber digitally-signed.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/o objstm, --object-streams=generate", + {$td->COMMAND => + "test_renumber --object-streams=generate minimal.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/ objstm, --object-streams=generate", + {$td->COMMAND => + "test_renumber --object-streams=generate digitally-signed.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/o objstm, --linearize", + {$td->COMMAND => + "test_renumber --linearize minimal.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/ objstm, --linearize", + {$td->COMMAND => + "test_renumber --linearize digitally-signed.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/o objstm, --preserve-unreferenced", + {$td->COMMAND => + "test_renumber --preserve-unreferenced minimal.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("w/ objstm, --preserve-unreferenced", + {$td->COMMAND => + "test_renumber --preserve-unreferenced digitally-signed.pdf"}, + {$td->REGEXP => "succeeded\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + show_ntests(); # ---------- $td->notify("--- Large File Tests ---"); diff --git a/qpdf/test_renumber.cc b/qpdf/test_renumber.cc new file mode 100644 index 00000000..1c1954cb --- /dev/null +++ b/qpdf/test_renumber.cc @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void usage() +{ + std::cerr + << "Usage: test_renumber [OPTION] INPUT.pdf" + << std::endl + << "Option:" + << std::endl + << " --object-streams=preserve|disable|generate" + << std::endl + << " --linearize" + << std::endl + << " --preserve-unreferenced" + << std::endl; +} + +bool compare(QPDFObjectHandle a, QPDFObjectHandle b) +{ + static std::set visited; + if (a.isIndirect()) + { + if (visited.count(a.getObjGen())) + { + return true; + } + visited.insert(a.getObjGen()); + } + + if (a.getTypeCode() != b.getTypeCode()) + { + std::cerr + << "different type code" + << std::endl; + return false; + } + + switch (a.getTypeCode()) + { + case QPDFObject::ot_boolean: + if (a.getBoolValue() != b.getBoolValue()) + { + std::cerr + << "different boolean" + << std::endl; + return false; + } + break; + case QPDFObject::ot_integer: + if (a.getIntValue() != b.getIntValue()) + { + std::cerr + << "different integer" + << std::endl; + return false; + } + break; + case QPDFObject::ot_real: + if (a.getRealValue() != b.getRealValue()) + { + std::cerr + << "different real" + << std::endl; + return false; + } + break; + case QPDFObject::ot_string: + if (a.getStringValue() != b.getStringValue()) + { + std::cerr + << "different string" + << std::endl; + return false; + } + break; + case QPDFObject::ot_name: + if (a.getName() != b.getName()) + { + std::cerr + << "different name" + << std::endl; + return false; + } + break; + case QPDFObject::ot_array: + { + std::vector objs_a = a.getArrayAsVector(); + std::vector objs_b = b.getArrayAsVector(); + size_t items = objs_a.size(); + if (items != objs_b.size()) + { + std::cerr + << "different array size" + << std::endl; + return false; + } + + for (size_t i = 0; i < items; ++i) + { + if (!compare(objs_a[i], objs_b[i])) + { + std::cerr + << "different array item" + << std::endl; + return false; + } + } + } + break; + case QPDFObject::ot_dictionary: + { + std::set keys_a = a.getKeys(); + std::set keys_b = b.getKeys(); + if (keys_a != keys_b) + { + std::cerr + << "different dictionary keys" + << std::endl; + return false; + } + + for(std::set::iterator iter = keys_a.begin(); + iter != keys_a.end(); ++iter) + { + if (!compare(a.getKey(*iter), b.getKey(*iter))) + { + std::cerr + << "different dictionary item" + << std::endl; + return false; + } + } + } + break; + case QPDFObject::ot_null: + break; + case QPDFObject::ot_stream: + std::cout << "stream objects are not compared" << std::endl; + break; + default: + std::cerr << "unknown object type" << std::endl; + std::exit(2); + } + + return true; +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + usage(); + std::exit(2); + } + + qpdf_object_stream_e mode = qpdf_o_preserve; + bool blinearize = false; + bool bpreserve_unreferenced = false; + std::string filename_input; + for (int i = 1; i < argc; ++i) + { + if (argv[i][0] == '-') + { + std::string opt = argv[i]; + if (opt == "--object-streams=preserve") + { + mode = qpdf_o_preserve; + } + else if (opt == "--object-streams=disable") + { + mode = qpdf_o_disable; + } + else if (opt == "--object-streams=generate") + { + mode = qpdf_o_generate; + } + else if (opt == "--linearize") + { + blinearize = true; + } + else if (opt == "--preserve-unreferenced") + { + bpreserve_unreferenced = true; + } + else + { + usage(); + std::exit(2); + } + } + else if (argc == i + 1) + { + filename_input = argv[i]; + break; + } + else + { + usage(); + std::exit(2); + } + } + + try + { + QPDF qpdf_in; + qpdf_in.processFile(filename_input.c_str ()); + std::vector objs_in = qpdf_in.getAllObjects(); + + QPDFWriter w(qpdf_in); + w.setOutputMemory(); + w.setObjectStreamMode(mode); + w.setLinearization(blinearize); + w.setPreserveUnreferencedObjects(bpreserve_unreferenced); + w.write(); + + PointerHolder buf = w.getBuffer(); + + QPDF qpdf_ren; + qpdf_ren.processMemoryFile("renumbered", + reinterpret_cast(buf->getBuffer()), + buf->getSize()); + + for (std::vector::iterator iter = objs_in.begin(); + iter != objs_in.end(); ++iter) + { + QPDFObjGen og_in = iter->getObjGen(); + QPDFObjGen og_ren = w.getRenumberedObjGen(og_in); + + std::cout + << "input " + << og_in.getObj() << "/" << og_in.getGen() + << " -> renumbered " + << og_ren.getObj() << "/" << og_ren.getGen() + << std::endl; + + if (og_ren.getObj() == 0) + { + std::cout << "deleted" << std::endl; + continue; + } + + if (!compare(*iter, qpdf_ren.getObjectByObjGen(og_ren))) + { + std::cerr + << "different" + << std::endl; + std::exit(2); + } + } + + std::cout << "succeeded" << std::endl; + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + std::exit(2); + } + + return 0; +}