blob: b35090c11eab16bb15d0f53f235af28aee5503a0 [file] [log] [blame]
//========================================================================
//
// pdfunite.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright (C) 2011-2014 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2012 Arseny Solokha <asolokha@gmx.com>
// Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2012, 2014 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
//
//========================================================================
#include <PDFDoc.h>
#include <GlobalParams.h>
#include "parseargs.h"
#include "config.h"
#include <poppler-config.h>
#include <vector>
static GBool printVersion = gFalse;
static GBool printHelp = gFalse;
static const ArgDesc argDesc[] = {
{"-v", argFlag, &printVersion, 0,
"print copyright and version info"},
{"-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[])
///////////////////////////////////////////////////////////////////////////
// Merge PDF files given by arguments 1 to argc-2 and write the result
// to the file specified by argument argc-1.
///////////////////////////////////////////////////////////////////////////
{
int objectsCount = 0;
Guint numOffset = 0;
std::vector<Object> pages;
std::vector<Guint> offsets;
XRef *yRef, *countRef;
FILE *f;
OutStream *outStr;
int i;
int j, rootNum;
std::vector<PDFDoc *>docs;
int majorVersion = 0;
int minorVersion = 0;
char *fileName = argv[argc - 1];
int exitCode;
exitCode = 99;
const GBool ok = parseArgs (argDesc, &argc, argv);
if (!ok || argc < 3 || printVersion || printHelp) {
fprintf(stderr, "pdfunite version %s\n", PACKAGE_VERSION);
fprintf(stderr, "%s\n", popplerCopyright);
fprintf(stderr, "%s\n", xpdfCopyright);
if (!printVersion) {
printUsage("pdfunite", "<PDF-sourcefile-1>..<PDF-sourcefile-n> <PDF-destfile>",
argDesc);
}
if (printVersion || printHelp)
exitCode = 0;
return exitCode;
}
exitCode = 0;
globalParams = new GlobalParams();
for (i = 1; i < argc - 1; i++) {
GooString *gfileName = new GooString(argv[i]);
PDFDoc *doc = new PDFDoc(gfileName, NULL, NULL, NULL);
if (doc->isOk() && !doc->isEncrypted()) {
docs.push_back(doc);
if (doc->getPDFMajorVersion() > majorVersion) {
majorVersion = doc->getPDFMajorVersion();
minorVersion = doc->getPDFMinorVersion();
} else if (doc->getPDFMajorVersion() == majorVersion) {
if (doc->getPDFMinorVersion() > minorVersion) {
minorVersion = doc->getPDFMinorVersion();
}
}
} else if (doc->isOk()) {
error(errUnimplemented, -1, "Could not merge encrypted files ('{0:s}')", argv[i]);
return -1;
} else {
error(errSyntaxError, -1, "Could not merge damaged documents ('{0:s}')", argv[i]);
return -1;
}
}
if (!(f = fopen(fileName, "wb"))) {
error(errIO, -1, "Could not open file '{0:s}'", fileName);
return -1;
}
outStr = new FileOutStream(f, 0);
yRef = new XRef();
countRef = new XRef();
yRef->add(0, 65535, 0, gFalse);
PDFDoc::writeHeader(outStr, majorVersion, minorVersion);
// handle OutputIntents, AcroForm & OCProperties
Object intents;
Object afObj;
Object ocObj;
if (docs.size() >= 1) {
Object catObj;
docs[0]->getXRef()->getCatalog(&catObj);
Dict *catDict = catObj.getDict();
catDict->lookup("OutputIntents", &intents);
catDict->lookupNF("AcroForm", &afObj);
Ref *refPage = docs[0]->getCatalog()->getPageRef(1);
if (!afObj.isNull()) {
docs[0]->markAcroForm(&afObj, yRef, countRef, 0, refPage->num, refPage->num);
}
catDict->lookupNF("OCProperties", &ocObj);
if (!ocObj.isNull() && ocObj.isDict()) {
docs[0]->markPageObjects(ocObj.getDict(), yRef, countRef, 0, refPage->num, refPage->num);
}
if (intents.isArray() && intents.arrayGetLength() > 0) {
for (i = 1; i < (int) docs.size(); i++) {
Object pagecatObj, pageintents;
docs[i]->getXRef()->getCatalog(&pagecatObj);
Dict *pagecatDict = pagecatObj.getDict();
pagecatDict->lookup("OutputIntents", &pageintents);
if (pageintents.isArray() && pageintents.arrayGetLength() > 0) {
for (j = intents.arrayGetLength() - 1; j >= 0; j--) {
Object intent;
intents.arrayGet(j, &intent, 0);
if (intent.isDict()) {
Object idf;
intent.dictLookup("OutputConditionIdentifier", &idf);
if (idf.isString()) {
GooString *gidf = idf.getString();
GBool removeIntent = gTrue;
for (int k = 0; k < pageintents.arrayGetLength(); k++) {
Object pgintent;
pageintents.arrayGet(k, &pgintent, 0);
if (pgintent.isDict()) {
Object pgidf;
pgintent.dictLookup("OutputConditionIdentifier", &pgidf);
if (pgidf.isString()) {
GooString *gpgidf = pgidf.getString();
if (gpgidf->cmp(gidf) == 0) {
pgidf.free();
removeIntent = gFalse;
break;
}
}
pgidf.free();
}
}
if (removeIntent) {
intents.arrayRemove(j);
error(errSyntaxWarning, -1, "Output intent {0:s} missing in pdf {1:s}, removed",
gidf->getCString(), docs[i]->getFileName()->getCString());
}
} else {
intents.arrayRemove(j);
error(errSyntaxWarning, -1, "Invalid output intent dict, missing required OutputConditionIdentifier");
}
idf.free();
} else {
intents.arrayRemove(j);
}
intent.free();
}
} else {
error(errSyntaxWarning, -1, "Output intents differs, remove them all");
intents.free();
break;
}
pagecatObj.free();
pageintents.free();
}
}
if (intents.isArray() && intents.arrayGetLength() > 0) {
for (j = intents.arrayGetLength() - 1; j >= 0; j--) {
Object intent;
intents.arrayGet(j, &intent, 0);
if (intent.isDict()) {
docs[0]->markPageObjects(intent.getDict(), yRef, countRef, numOffset, 0, 0);
} else {
intents.arrayRemove(j);
}
intent.free();
}
}
catObj.free();
}
for (i = 0; i < (int) docs.size(); i++) {
for (j = 1; j <= docs[i]->getNumPages(); j++) {
PDFRectangle *cropBox = NULL;
if (docs[i]->getCatalog()->getPage(j)->isCropped())
cropBox = docs[i]->getCatalog()->getPage(j)->getCropBox();
docs[i]->replacePageDict(j,
docs[i]->getCatalog()->getPage(j)->getRotate(),
docs[i]->getCatalog()->getPage(j)->getMediaBox(), cropBox);
Ref *refPage = docs[i]->getCatalog()->getPageRef(j);
Object page;
docs[i]->getXRef()->fetch(refPage->num, refPage->gen, &page);
Dict *pageDict = page.getDict();
Dict *resDict = docs[i]->getCatalog()->getPage(j)->getResourceDict();
if (resDict) {
Object *newResource = new Object();
newResource->initDict(resDict);
pageDict->set("Resources", newResource);
}
pages.push_back(page);
offsets.push_back(numOffset);
docs[i]->markPageObjects(pageDict, yRef, countRef, numOffset, refPage->num, refPage->num);
Object annotsObj;
pageDict->lookupNF("Annots", &annotsObj);
if (!annotsObj.isNull()) {
docs[i]->markAnnotations(&annotsObj, yRef, countRef, numOffset, refPage->num, refPage->num);
annotsObj.free();
}
}
objectsCount += docs[i]->writePageObjects(outStr, yRef, numOffset, gTrue);
numOffset = yRef->getNumObjects() + 1;
}
rootNum = yRef->getNumObjects() + 1;
yRef->add(rootNum, 0, outStr->getPos(), gTrue);
outStr->printf("%d 0 obj\n", rootNum);
outStr->printf("<< /Type /Catalog /Pages %d 0 R", rootNum + 1);
// insert OutputIntents
if (intents.isArray() && intents.arrayGetLength() > 0) {
outStr->printf(" /OutputIntents [");
for (j = 0; j < intents.arrayGetLength(); j++) {
Object intent;
intents.arrayGet(j, &intent, 0);
if (intent.isDict()) {
PDFDoc::writeObject(&intent, outStr, yRef, 0, NULL, cryptRC4, 0, 0, 0);
}
intent.free();
}
outStr->printf("]");
}
intents.free();
// insert AcroForm
if (!afObj.isNull()) {
outStr->printf(" /AcroForm ");
PDFDoc::writeObject(&afObj, outStr, yRef, 0, NULL, cryptRC4, 0, 0, 0);
afObj.free();
}
// insert OCProperties
if (!ocObj.isNull() && ocObj.isDict()) {
outStr->printf(" /OCProperties ");
PDFDoc::writeObject(&ocObj, outStr, yRef, 0, NULL, cryptRC4, 0, 0, 0);
ocObj.free();
}
outStr->printf(">>\nendobj\n");
objectsCount++;
yRef->add(rootNum + 1, 0, outStr->getPos(), gTrue);
outStr->printf("%d 0 obj\n", rootNum + 1);
outStr->printf("<< /Type /Pages /Kids [");
for (j = 0; j < (int) pages.size(); j++)
outStr->printf(" %d 0 R", rootNum + j + 2);
outStr->printf(" ] /Count %zd >>\nendobj\n", pages.size());
objectsCount++;
for (i = 0; i < (int) pages.size(); i++) {
yRef->add(rootNum + i + 2, 0, outStr->getPos(), gTrue);
outStr->printf("%d 0 obj\n", rootNum + i + 2);
outStr->printf("<< ");
Dict *pageDict = pages[i].getDict();
for (j = 0; j < pageDict->getLength(); j++) {
if (j > 0)
outStr->printf(" ");
const char *key = pageDict->getKey(j);
Object value;
pageDict->getValNF(j, &value);
if (strcmp(key, "Parent") == 0) {
outStr->printf("/Parent %d 0 R", rootNum + 1);
} else {
outStr->printf("/%s ", key);
PDFDoc::writeObject(&value, outStr, yRef, offsets[i], NULL, cryptRC4, 0, 0, 0);
}
value.free();
}
outStr->printf(" >>\nendobj\n");
objectsCount++;
}
Goffset uxrefOffset = outStr->getPos();
Ref ref;
ref.num = rootNum;
ref.gen = 0;
Dict *trailerDict = PDFDoc::createTrailerDict(objectsCount, gFalse, 0, &ref, yRef,
fileName, outStr->getPos());
PDFDoc::writeXRefTableTrailer(trailerDict, yRef, gFalse /* do not write unnecessary entries */,
uxrefOffset, outStr, yRef);
delete trailerDict;
outStr->close();
fclose(f);
delete yRef;
delete countRef;
for (j = 0; j < (int) pages.size (); j++) pages[j].free();
for (i = 0; i < (int) docs.size (); i++) delete docs[i];
delete globalParams;
return exitCode;
}