diff options
-rw-r--r-- | test/CMakeLists.txt | 1 | ||||
-rw-r--r-- | test/Makefile.am | 3 | ||||
-rw-r--r-- | test/pdf-fullrewrite.cc | 354 |
3 files changed, 342 insertions, 16 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 178b15e1..a89a4cfb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,6 +44,7 @@ endif (GTK_FOUND) set (pdf_fullrewrite_SRCS pdf-fullrewrite.cc + ../utils/parseargs.cc ) add_executable(pdf-fullrewrite ${pdf_fullrewrite_SRCS}) target_link_libraries(pdf-fullrewrite poppler) diff --git a/test/Makefile.am b/test/Makefile.am index 619d6718..b3289c72 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -76,7 +76,8 @@ perf_test_LDADD = \ $(X_EXTRA_LIBS) pdf_fullrewrite_SOURCES = \ - pdf-fullrewrite.cc + pdf-fullrewrite.cc \ + ../utils/parseargs.cc pdf_fullrewrite_LDADD = \ $(top_builddir)/poppler/libpoppler.la diff --git a/test/pdf-fullrewrite.cc b/test/pdf-fullrewrite.cc index 9658e55a..2b912f7e 100644 --- a/test/pdf-fullrewrite.cc +++ b/test/pdf-fullrewrite.cc @@ -3,44 +3,368 @@ // pdf-fullrewrite.cc // // Copyright 2007 Julien Rebetez +// Copyright 2012 Fabio D'Urso // //======================================================================== -#include "config.h" -#include <poppler-config.h> + #include "GlobalParams.h" #include "Error.h" +#include "Object.h" #include "PDFDoc.h" +#include "XRef.h" #include "goo/GooString.h" +#include "utils/parseargs.h" + +static GBool compareDocuments(PDFDoc *origDoc, PDFDoc *newDoc); +static GBool compareObjects(Object *objA, Object *objB); + +static char ownerPassword[33] = "\001"; +static char userPassword[33] = "\001"; +static GBool forceIncremental = gFalse; +static GBool checkOutput = gFalse; +static GBool printHelp = gFalse; + +static const ArgDesc argDesc[] = { + {"-opw", argString, ownerPassword, sizeof(ownerPassword), + "owner password (for encrypted files)"}, + {"-upw", argString, userPassword, sizeof(userPassword), + "user password (for encrypted files)"}, + {"-i", argFlag, &forceIncremental,0, + "incremental update mode"}, + {"-check", argFlag, &checkOutput, 0, + "verify the generated document"}, + {"-h", argFlag, &printHelp, 0, + "print usage information"}, + {"-help", argFlag, &printHelp, 0, + "print usage information"}, + {"--help", argFlag, &printHelp, 0, + "print usage information"}, + {"-?", argFlag, &printHelp, 0, + "print usage information"}, + {NULL} +}; int main (int argc, char *argv[]) { - PDFDoc *doc; - GooString *inputName, *outputName; + PDFDoc *doc = NULL; + PDFDoc *docOut = NULL; + GooString *inputName = NULL; + GooString *outputName = NULL; + GooString *ownerPW = NULL; + GooString *userPW = NULL; + int res = 0; // parse args - if (argc < 3) { - fprintf(stderr, "usage: %s INPUT-FILE OUTPUT-FILE\n", argv[0]); - return 1; + GBool ok = parseArgs(argDesc, &argc, argv); + if (!ok || (argc < 3) || printHelp) { + printUsage(argv[0], "INPUT-FILE OUTPUT-FILE", argDesc); + if (!printHelp) { + res = 1; + } + goto done; } inputName = new GooString(argv[1]); outputName = new GooString(argv[2]); - globalParams = new GlobalParams(); - - doc = new PDFDoc(inputName); + if (ownerPassword[0] != '\001') { + ownerPW = new GooString(ownerPassword); + } + if (userPassword[0] != '\001') { + userPW = new GooString(userPassword); + } + // load input document + globalParams = new GlobalParams(); + doc = new PDFDoc(inputName, ownerPW, userPW); if (!doc->isOk()) { - delete doc; - fprintf(stderr, "Error loading document !\n"); - return 1; + fprintf(stderr, "Error loading input document\n"); + res = 1; + goto done; } + // save it back (in rewrite or incremental update mode) + if (doc->saveAs(outputName, forceIncremental ? writeForceIncremental : writeForceRewrite) != 0) { + fprintf(stderr, "Error saving document\n"); + res = 1; + goto done; + } - int res = doc->saveAs(outputName, writeForceRewrite); + if (checkOutput) { + // open the generated document to verify it + docOut = new PDFDoc(outputName, ownerPW, userPW); + if (!docOut->isOk()) { + fprintf(stderr, "Error loading generated document\n"); + res = 1; + } else if (!compareDocuments(doc, docOut)) { + fprintf(stderr, "Verification failed\n"); + res = 1; + } + } else { + delete outputName; + } +done: + delete docOut; delete doc; delete globalParams; - delete outputName; + delete userPW; + delete ownerPW; return res; } + +static GBool compareDictionaries(Dict *dictA, Dict *dictB) +{ + const int length = dictA->getLength(); + if (dictB->getLength() != length) + return gFalse; + + /* Check that every key in dictA is contained in dictB. + * Since keys are unique and we've already checked that dictA and dictB + * contain the same number of entries, we don't need to check that every key + * in dictB is also contained in dictA */ + for (int i = 0; i < length; ++i) { + Object valA, valB; + const char *key = dictA->getKey(i); + dictA->getValNF(i, &valA); + dictB->lookupNF(key, &valB); + if (!compareObjects(&valA, &valB)) + return gFalse; + valA.free(); + valB.free(); + } + + return gTrue; +} + +static GBool compareObjects(Object *objA, Object *objB) +{ + switch (objA->getType()) { + case objBool: + { + if (objB->getType() != objBool) { + return gFalse; + } else { + return (objA->getBool() == objB->getBool()); + } + } + case objInt: + case objReal: + { + if (!objB->isNum()) { + return gFalse; + } else { + // Fuzzy comparison + const double diff = objA->getNum() - objB->getNum(); + return (-0.01 < diff) && (diff < 0.01); + } + } + case objUint: + { + if (objB->getType() != objUint) { + return gFalse; + } else { + return (objA->getUint() == objB->getUint()); + } + } + case objString: + { + if (objB->getType() != objString) { + return gFalse; + } else { + GooString *strA = objA->getString(); + GooString *strB = objB->getString(); + return (strA->cmp(strB) == 0); + } + } + case objName: + { + if (objB->getType() != objName) { + return gFalse; + } else { + GooString nameA(objA->getName()); + GooString nameB(objB->getName()); + return (nameA.cmp(&nameB) == 0); + } + } + case objNull: + { + if (objB->getType() != objNull) { + return gFalse; + } else { + return gTrue; + } + } + case objArray: + { + if (objB->getType() != objArray) { + return gFalse; + } else { + Array *arrayA = objA->getArray(); + Array *arrayB = objB->getArray(); + const int length = arrayA->getLength(); + if (arrayB->getLength() != length) { + return gFalse; + } else { + for (int i = 0; i < length; ++i) { + Object elemA, elemB; + arrayA->getNF(i, &elemA); + arrayB->getNF(i, &elemB); + if (!compareObjects(&elemA, &elemB)) { + return gFalse; + } + elemA.free(); + elemB.free(); + } + return gTrue; + } + } + } + case objDict: + { + if (objB->getType() != objDict) { + return gFalse; + } else { + Dict *dictA = objA->getDict(); + Dict *dictB = objB->getDict(); + return compareDictionaries(dictA, dictB); + } + } + case objStream: + { + if (objB->getType() != objStream) { + return gFalse; + } else { + Stream *streamA = objA->getStream(); + Stream *streamB = objB->getStream(); + if (!compareDictionaries(streamA->getDict(), streamB->getDict())) { + return gFalse; + } else { + int c; + streamA->reset(); + streamB->reset(); + do + { + c = streamA->getChar(); + if (c != streamB->getChar()) { + return gFalse; + } + } while (c != EOF); + return gTrue; + } + } + return gTrue; + } + case objRef: + { + if (objB->getType() != objRef) { + return gFalse; + } else { + Ref refA = objA->getRef(); + Ref refB = objB->getRef(); + return (refA.num == refB.num) && (refA.gen == refB.gen); + } + } + default: + { + fprintf(stderr, "compareObjects failed: unexpected object type %u\n", objA->getType()); + return gFalse; + } + } +} + +static GBool compareDocuments(PDFDoc *origDoc, PDFDoc *newDoc) +{ + GBool result = gTrue; + XRef *origXRef = origDoc->getXRef(); + XRef *newXRef = newDoc->getXRef(); + + // Make sure that special flags are set in both documents + origXRef->scanSpecialFlags(); + newXRef->scanSpecialFlags(); + + // Compare XRef tables' size + const int origNumObjects = origXRef->getNumObjects(); + const int newNumObjects = newXRef->getNumObjects(); + if (forceIncremental && origXRef->isXRefStream()) { + // In case of incremental update, expect a new entry to be appended to store the new XRef stream + if (origNumObjects+1 != newNumObjects) { + fprintf(stderr, "XRef table: Unexpected number of entries (%d+1 != %d)\n", origNumObjects, newNumObjects); + result = gFalse; + } + } else { + // In all other cases the number of entries must be the same + if (origNumObjects != newNumObjects) { + fprintf(stderr, "XRef table: Different number of entries (%d != %d)\n", origNumObjects, newNumObjects); + result = gFalse; + } + } + + // Compare each XRef entry + const int numObjects = (origNumObjects < newNumObjects) ? origNumObjects : newNumObjects; + for (int i = 0; i < numObjects; ++i) { + XRefEntryType origType = origXRef->getEntry(i)->type; + XRefEntryType newType = newXRef->getEntry(i)->type; + const int origGenNum = (origType != xrefEntryCompressed) ? origXRef->getEntry(i)->gen : 0; + const int newGenNum = (newType != xrefEntryCompressed) ? newXRef->getEntry(i)->gen : 0; + + // Check that DontRewrite entries are freed in full rewrite mode + if (!forceIncremental && origXRef->getEntry(i)->getFlag(XRefEntry::DontRewrite)) { + if (newType != xrefEntryFree || origGenNum+1 != newGenNum) { + fprintf(stderr, "XRef entry %u: DontRewrite entry was not freed correctly\n", i); + result = gFalse; + } + continue; // There's nothing left to check for this entry + } + + // Compare generation numbers + // Object num 0 should always have gen 65535 according to specs, but some + // documents have it set to 0. We always write 65535 in output + if (i != 0) { + if (origGenNum != newGenNum) { + fprintf(stderr, "XRef entry %u: generation numbers differ (%d != %d)\n", i, origGenNum, newGenNum); + result = gFalse; + continue; + } + } else { + if (newGenNum != 65535) { + fprintf(stderr, "XRef entry %u: generation number was expected to be 65535 (%d != 65535)\n", i, newGenNum); + result = gFalse; + continue; + } + } + + // Compare object flags. A failure shows that there's some error in XRef::scanSpecialFlags() + if (origXRef->getEntry(i)->flags != newXRef->getEntry(i)->flags) { + fprintf(stderr, "XRef entry %u: flags detected by scanSpecialFlags differ (%d != %d)\n", i, origXRef->getEntry(i)->flags, newXRef->getEntry(i)->flags); + result = gFalse; + } + + // Check that either both are free or both are in use + if ((origType == xrefEntryFree) != (newType == xrefEntryFree)) { + const char *origStatus = (origType == xrefEntryFree) ? "free" : "in use"; + const char *newStatus = (newType == xrefEntryFree) ? "free" : "in use"; + fprintf(stderr, "XRef entry %u: usage status differs (%s != %s)\n", i, origStatus, newStatus); + result = gFalse; + continue; + } + + // Skip free entries + if (origType == xrefEntryFree) { + continue; + } + + // Compare contents + Object origObj, newObj; + origXRef->fetch(i, origGenNum, &origObj); + newXRef->fetch(i, newGenNum, &newObj); + if (!compareObjects(&origObj, &newObj)) { + fprintf(stderr, "XRef entry %u: contents differ\n", i); + result = gFalse; + } + origObj.free(); + newObj.free(); + } + + return result; +} |