summaryrefslogtreecommitdiff
path: root/Build/source/libs/poppler/poppler-0.26.2/poppler/PDFDoc.cc
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/libs/poppler/poppler-0.26.2/poppler/PDFDoc.cc')
-rw-r--r--Build/source/libs/poppler/poppler-0.26.2/poppler/PDFDoc.cc1941
1 files changed, 1941 insertions, 0 deletions
diff --git a/Build/source/libs/poppler/poppler-0.26.2/poppler/PDFDoc.cc b/Build/source/libs/poppler/poppler-0.26.2/poppler/PDFDoc.cc
new file mode 100644
index 00000000000..e427d2b40ff
--- /dev/null
+++ b/Build/source/libs/poppler/poppler-0.26.2/poppler/PDFDoc.cc
@@ -0,0 +1,1941 @@
+//========================================================================
+//
+// PDFDoc.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2005, 2006, 2008 Brad Hards <bradh@frogmouth.net>
+// Copyright (C) 2005, 2007-2009, 2011-2014 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2008 Julien Rebetez <julienr@svn.gnome.org>
+// Copyright (C) 2008, 2010 Pino Toscano <pino@kde.org>
+// Copyright (C) 2008, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright (C) 2009 Eric Toombs <ewtoombs@uwaterloo.ca>
+// Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net>
+// Copyright (C) 2009, 2011 Axel Struebing <axel.struebing@freenet.de>
+// Copyright (C) 2010-2012 Hib Eris <hib@hiberis.nl>
+// Copyright (C) 2010 Jakub Wilk <ubanus@users.sf.net>
+// Copyright (C) 2010 Ilya Gorenbein <igorenbein@finjan.com>
+// Copyright (C) 2010 Srinivas Adicherla <srinivas.adicherla@geodesic.com>
+// Copyright (C) 2010 Philip Lorenz <lorenzph+freedesktop@gmail.com>
+// Copyright (C) 2011-2014 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2013 Adam Reichold <adamreichold@myopera.com>
+// Copyright (C) 2014 Bogdan Cristea <cristeab@gmail.com>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+#include <config.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <ctype.h>
+#include <locale.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include "goo/gstrtod.h"
+#include "goo/GooString.h"
+#include "goo/gfile.h"
+#include "poppler-config.h"
+#include "GlobalParams.h"
+#include "Page.h"
+#include "Catalog.h"
+#include "Stream.h"
+#include "XRef.h"
+#include "Linearization.h"
+#include "Link.h"
+#include "OutputDev.h"
+#include "Error.h"
+#include "ErrorCodes.h"
+#include "Lexer.h"
+#include "Parser.h"
+#include "SecurityHandler.h"
+#include "Decrypt.h"
+#ifndef DISABLE_OUTLINE
+#include "Outline.h"
+#endif
+#include "PDFDoc.h"
+#include "Hints.h"
+
+#if MULTITHREADED
+# define pdfdocLocker() MutexLocker locker(&mutex)
+#else
+# define pdfdocLocker()
+#endif
+
+//------------------------------------------------------------------------
+
+#define headerSearchSize 1024 // read this many bytes at beginning of
+ // file to look for '%PDF'
+#define pdfIdLength 32 // PDF Document IDs (PermanentId, UpdateId) length
+
+#define linearizationSearchSize 1024 // read this many bytes at beginning of
+ // file to look for linearization
+ // dictionary
+
+#define xrefSearchSize 1024 // read this many bytes at end of file
+ // to look for 'startxref'
+
+//------------------------------------------------------------------------
+// PDFDoc
+//------------------------------------------------------------------------
+
+void PDFDoc::init()
+{
+#if MULTITHREADED
+ gInitMutex(&mutex);
+#endif
+ ok = gFalse;
+ errCode = errNone;
+ fileName = NULL;
+ file = NULL;
+ str = NULL;
+ xref = NULL;
+ linearization = NULL;
+ catalog = NULL;
+ hints = NULL;
+#ifndef DISABLE_OUTLINE
+ outline = NULL;
+#endif
+ startXRefPos = -1;
+ secHdlr = NULL;
+ pageCache = NULL;
+}
+
+PDFDoc::PDFDoc()
+{
+ init();
+}
+
+PDFDoc::PDFDoc(GooString *fileNameA, GooString *ownerPassword,
+ GooString *userPassword, void *guiDataA) {
+ Object obj;
+#ifdef _WIN32
+ int n, i;
+#endif
+
+ init();
+
+ fileName = fileNameA;
+ guiData = guiDataA;
+#ifdef _WIN32
+ n = fileName->getLength();
+ fileNameU = (wchar_t *)gmallocn(n + 1, sizeof(wchar_t));
+ for (i = 0; i < n; ++i) {
+ fileNameU[i] = (wchar_t)(fileName->getChar(i) & 0xff);
+ }
+ fileNameU[n] = L'\0';
+#endif
+
+ // try to open file
+ file = GooFile::open(fileName);
+ if (file == NULL) {
+ // fopen() has failed.
+ // Keep a copy of the errno returned by fopen so that it can be
+ // referred to later.
+ fopenErrno = errno;
+ error(errIO, -1, "Couldn't open file '{0:t}': {1:s}.", fileName, strerror(errno));
+ errCode = errOpenFile;
+ return;
+ }
+
+ // create stream
+ obj.initNull();
+ str = new FileStream(file, 0, gFalse, file->size(), &obj);
+
+ ok = setup(ownerPassword, userPassword);
+}
+
+#ifdef _WIN32
+PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GooString *ownerPassword,
+ GooString *userPassword, void *guiDataA) {
+ OSVERSIONINFO version;
+ Object obj;
+ int i;
+
+ init();
+
+ guiData = guiDataA;
+
+ // save both Unicode and 8-bit copies of the file name
+ fileName = new GooString();
+ fileNameU = (wchar_t *)gmallocn(fileNameLen + 1, sizeof(wchar_t));
+ for (i = 0; i < fileNameLen; ++i) {
+ fileName->append((char)fileNameA[i]);
+ fileNameU[i] = fileNameA[i];
+ }
+ fileNameU[fileNameLen] = L'\0';
+
+ // try to open file
+ // NB: _wfopen is only available in NT
+ version.dwOSVersionInfoSize = sizeof(version);
+ GetVersionEx(&version);
+ if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
+ file = GooFile::open(fileNameU);
+ } else {
+ file = GooFile::open(fileName);
+ }
+ if (!file) {
+ error(errIO, -1, "Couldn't open file '{0:t}'", fileName);
+ errCode = errOpenFile;
+ return;
+ }
+
+ // create stream
+ obj.initNull();
+ str = new FileStream(file, 0, gFalse, file->size(), &obj);
+
+ ok = setup(ownerPassword, userPassword);
+}
+#endif
+
+PDFDoc::PDFDoc(BaseStream *strA, GooString *ownerPassword,
+ GooString *userPassword, void *guiDataA) {
+#ifdef _WIN32
+ int n, i;
+#endif
+
+ init();
+ guiData = guiDataA;
+ if (strA->getFileName()) {
+ fileName = strA->getFileName()->copy();
+#ifdef _WIN32
+ n = fileName->getLength();
+ fileNameU = (wchar_t *)gmallocn(n + 1, sizeof(wchar_t));
+ for (i = 0; i < n; ++i) {
+ fileNameU[i] = (wchar_t)(fileName->getChar(i) & 0xff);
+ }
+ fileNameU[n] = L'\0';
+#endif
+ } else {
+ fileName = NULL;
+#ifdef _WIN32
+ fileNameU = NULL;
+#endif
+ }
+ str = strA;
+ ok = setup(ownerPassword, userPassword);
+}
+
+GBool PDFDoc::setup(GooString *ownerPassword, GooString *userPassword) {
+ pdfdocLocker();
+ str->setPos(0, -1);
+ if (str->getPos() < 0)
+ {
+ error(errSyntaxError, -1, "Document base stream is not seekable");
+ return gFalse;
+ }
+
+ str->reset();
+
+ // check footer
+ // Adobe does not seem to enforce %%EOF, so we do the same
+// if (!checkFooter()) return gFalse;
+
+ // check header
+ checkHeader();
+
+ GBool wasReconstructed = false;
+
+ // read xref table
+ xref = new XRef(str, getStartXRef(), getMainXRefEntriesOffset(), &wasReconstructed);
+ if (!xref->isOk()) {
+ if (wasReconstructed) {
+ delete xref;
+ startXRefPos = -1;
+ xref = new XRef(str, getStartXRef(gTrue), getMainXRefEntriesOffset(gTrue), &wasReconstructed);
+ }
+ if (!xref->isOk()) {
+ error(errSyntaxError, -1, "Couldn't read xref table");
+ errCode = xref->getErrorCode();
+ return gFalse;
+ }
+ }
+
+ // check for encryption
+ if (!checkEncryption(ownerPassword, userPassword)) {
+ errCode = errEncrypted;
+ return gFalse;
+ }
+
+ // read catalog
+ catalog = new Catalog(this);
+ if (catalog && !catalog->isOk()) {
+ if (!wasReconstructed)
+ {
+ // try one more time to contruct the Catalog, maybe the problem is damaged XRef
+ delete catalog;
+ delete xref;
+ xref = new XRef(str, 0, 0, NULL, true);
+ catalog = new Catalog(this);
+ }
+
+ if (catalog && !catalog->isOk()) {
+ error(errSyntaxError, -1, "Couldn't read page catalog");
+ errCode = errBadCatalog;
+ return gFalse;
+ }
+ }
+
+ // done
+ return gTrue;
+}
+
+PDFDoc::~PDFDoc() {
+ if (pageCache) {
+ for (int i = 0; i < getNumPages(); i++) {
+ if (pageCache[i]) {
+ delete pageCache[i];
+ }
+ }
+ gfree(pageCache);
+ }
+ delete secHdlr;
+#ifndef DISABLE_OUTLINE
+ if (outline) {
+ delete outline;
+ }
+#endif
+ if (catalog) {
+ delete catalog;
+ }
+ if (xref) {
+ delete xref;
+ }
+ if (hints) {
+ delete hints;
+ }
+ if (linearization) {
+ delete linearization;
+ }
+ if (str) {
+ delete str;
+ }
+ if (file) {
+ delete file;
+ }
+ if (fileName) {
+ delete fileName;
+ }
+#ifdef _WIN32
+ if (fileNameU) {
+ gfree(fileNameU);
+ }
+#endif
+#if MULTITHREADED
+ gDestroyMutex(&mutex);
+#endif
+}
+
+
+// Check for a %%EOF at the end of this stream
+GBool PDFDoc::checkFooter() {
+ // we look in the last 1024 chars because Adobe does the same
+ char *eof = new char[1025];
+ Goffset pos = str->getPos();
+ str->setPos(1024, -1);
+ int i, ch;
+ for (i = 0; i < 1024; i++)
+ {
+ ch = str->getChar();
+ if (ch == EOF)
+ break;
+ eof[i] = ch;
+ }
+ eof[i] = '\0';
+
+ bool found = false;
+ for (i = i - 5; i >= 0; i--) {
+ if (strncmp (&eof[i], "%%EOF", 5) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ error(errSyntaxError, -1, "Document has not the mandatory ending %%EOF");
+ errCode = errDamaged;
+ delete[] eof;
+ return gFalse;
+ }
+ delete[] eof;
+ str->setPos(pos);
+ return gTrue;
+}
+
+// Check for a PDF header on this stream. Skip past some garbage
+// if necessary.
+void PDFDoc::checkHeader() {
+ char hdrBuf[headerSearchSize+1];
+ char *p;
+ char *tokptr;
+ int i;
+
+ pdfMajorVersion = 0;
+ pdfMinorVersion = 0;
+ for (i = 0; i < headerSearchSize; ++i) {
+ hdrBuf[i] = str->getChar();
+ }
+ hdrBuf[headerSearchSize] = '\0';
+ for (i = 0; i < headerSearchSize - 5; ++i) {
+ if (!strncmp(&hdrBuf[i], "%PDF-", 5)) {
+ break;
+ }
+ }
+ if (i >= headerSearchSize - 5) {
+ error(errSyntaxWarning, -1, "May not be a PDF file (continuing anyway)");
+ return;
+ }
+ str->moveStart(i);
+ if (!(p = strtok_r(&hdrBuf[i+5], " \t\n\r", &tokptr))) {
+ error(errSyntaxWarning, -1, "May not be a PDF file (continuing anyway)");
+ return;
+ }
+ sscanf(p, "%d.%d", &pdfMajorVersion, &pdfMinorVersion);
+ // We don't do the version check. Don't add it back in.
+}
+
+GBool PDFDoc::checkEncryption(GooString *ownerPassword, GooString *userPassword) {
+ Object encrypt;
+ GBool encrypted;
+ GBool ret;
+
+ xref->getTrailerDict()->dictLookup("Encrypt", &encrypt);
+ if ((encrypted = encrypt.isDict())) {
+ if ((secHdlr = SecurityHandler::make(this, &encrypt))) {
+ if (secHdlr->isUnencrypted()) {
+ // no encryption
+ ret = gTrue;
+ } else if (secHdlr->checkEncryption(ownerPassword, userPassword)) {
+ // authorization succeeded
+ xref->setEncryption(secHdlr->getPermissionFlags(),
+ secHdlr->getOwnerPasswordOk(),
+ secHdlr->getFileKey(),
+ secHdlr->getFileKeyLength(),
+ secHdlr->getEncVersion(),
+ secHdlr->getEncRevision(),
+ secHdlr->getEncAlgorithm());
+ ret = gTrue;
+ } else {
+ // authorization failed
+ ret = gFalse;
+ }
+ } else {
+ // couldn't find the matching security handler
+ ret = gFalse;
+ }
+ } else {
+ // document is not encrypted
+ ret = gTrue;
+ }
+ encrypt.free();
+ return ret;
+}
+
+void PDFDoc::displayPage(OutputDev *out, int page,
+ double hDPI, double vDPI, int rotate,
+ GBool useMediaBox, GBool crop, GBool printing,
+ GBool (*abortCheckCbk)(void *data),
+ void *abortCheckCbkData,
+ GBool (*annotDisplayDecideCbk)(Annot *annot, void *user_data),
+ void *annotDisplayDecideCbkData, GBool copyXRef) {
+ if (globalParams->getPrintCommands()) {
+ printf("***** page %d *****\n", page);
+ }
+
+ if (getPage(page))
+ getPage(page)->display(out, hDPI, vDPI,
+ rotate, useMediaBox, crop, printing,
+ abortCheckCbk, abortCheckCbkData,
+ annotDisplayDecideCbk, annotDisplayDecideCbkData, copyXRef);
+
+}
+
+void PDFDoc::displayPages(OutputDev *out, int firstPage, int lastPage,
+ double hDPI, double vDPI, int rotate,
+ GBool useMediaBox, GBool crop, GBool printing,
+ GBool (*abortCheckCbk)(void *data),
+ void *abortCheckCbkData,
+ GBool (*annotDisplayDecideCbk)(Annot *annot, void *user_data),
+ void *annotDisplayDecideCbkData) {
+ int page;
+
+ for (page = firstPage; page <= lastPage; ++page) {
+ displayPage(out, page, hDPI, vDPI, rotate, useMediaBox, crop, printing,
+ abortCheckCbk, abortCheckCbkData,
+ annotDisplayDecideCbk, annotDisplayDecideCbkData);
+ }
+}
+
+void PDFDoc::displayPageSlice(OutputDev *out, int page,
+ double hDPI, double vDPI, int rotate,
+ GBool useMediaBox, GBool crop, GBool printing,
+ int sliceX, int sliceY, int sliceW, int sliceH,
+ GBool (*abortCheckCbk)(void *data),
+ void *abortCheckCbkData,
+ GBool (*annotDisplayDecideCbk)(Annot *annot, void *user_data),
+ void *annotDisplayDecideCbkData, GBool copyXRef) {
+ if (getPage(page))
+ getPage(page)->displaySlice(out, hDPI, vDPI,
+ rotate, useMediaBox, crop,
+ sliceX, sliceY, sliceW, sliceH,
+ printing,
+ abortCheckCbk, abortCheckCbkData,
+ annotDisplayDecideCbk, annotDisplayDecideCbkData, copyXRef);
+}
+
+Links *PDFDoc::getLinks(int page) {
+ Page *p = getPage(page);
+ if (!p) {
+ return new Links (NULL);
+ }
+ return p->getLinks();
+}
+
+void PDFDoc::processLinks(OutputDev *out, int page) {
+ if (getPage(page))
+ getPage(page)->processLinks(out);
+}
+
+Linearization *PDFDoc::getLinearization()
+{
+ if (!linearization) {
+ linearization = new Linearization(str);
+ }
+ return linearization;
+}
+
+GBool PDFDoc::isLinearized(GBool tryingToReconstruct) {
+ if ((str->getLength()) &&
+ (getLinearization()->getLength() == str->getLength()))
+ return gTrue;
+ else {
+ if (tryingToReconstruct)
+ return getLinearization()->getLength() > 0;
+ else
+ return gFalse;
+ }
+}
+
+static GBool
+get_id (GooString *encodedidstring, GooString *id) {
+ const char *encodedid = encodedidstring->getCString();
+ char pdfid[pdfIdLength + 1];
+ int n;
+
+ if (encodedidstring->getLength() != pdfIdLength / 2)
+ return gFalse;
+
+ n = sprintf(pdfid, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+ encodedid[0] & 0xff, encodedid[1] & 0xff, encodedid[2] & 0xff, encodedid[3] & 0xff,
+ encodedid[4] & 0xff, encodedid[5] & 0xff, encodedid[6] & 0xff, encodedid[7] & 0xff,
+ encodedid[8] & 0xff, encodedid[9] & 0xff, encodedid[10] & 0xff, encodedid[11] & 0xff,
+ encodedid[12] & 0xff, encodedid[13] & 0xff, encodedid[14] & 0xff, encodedid[15] & 0xff);
+ if (n != pdfIdLength)
+ return gFalse;
+
+ id->Set(pdfid, pdfIdLength);
+ return gTrue;
+}
+
+GBool PDFDoc::getID(GooString *permanent_id, GooString *update_id) {
+ Object obj;
+ xref->getTrailerDict()->dictLookup ("ID", &obj);
+
+ if (obj.isArray() && obj.arrayGetLength() == 2) {
+ Object obj2;
+
+ if (permanent_id) {
+ if (obj.arrayGet(0, &obj2)->isString()) {
+ if (!get_id (obj2.getString(), permanent_id)) {
+ obj2.free();
+ return gFalse;
+ }
+ } else {
+ error(errSyntaxError, -1, "Invalid permanent ID");
+ obj2.free();
+ return gFalse;
+ }
+ obj2.free();
+ }
+
+ if (update_id) {
+ if (obj.arrayGet(1, &obj2)->isString()) {
+ if (!get_id (obj2.getString(), update_id)) {
+ obj2.free();
+ return gFalse;
+ }
+ } else {
+ error(errSyntaxError, -1, "Invalid update ID");
+ obj2.free();
+ return gFalse;
+ }
+ obj2.free();
+ }
+
+ obj.free();
+
+ return gTrue;
+ }
+ obj.free();
+
+ return gFalse;
+}
+
+Hints *PDFDoc::getHints()
+{
+ if (!hints && isLinearized()) {
+ hints = new Hints(str, getLinearization(), getXRef(), secHdlr);
+ }
+
+ return hints;
+}
+
+int PDFDoc::savePageAs(GooString *name, int pageNo)
+{
+ FILE *f;
+ OutStream *outStr;
+ XRef *yRef, *countRef;
+ int rootNum = getXRef()->getNumObjects() + 1;
+
+ // Make sure that special flags are set, because we are going to read
+ // all objects, including Unencrypted ones.
+ xref->scanSpecialFlags();
+
+ Guchar *fileKey;
+ CryptAlgorithm encAlgorithm;
+ int keyLength;
+ xref->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
+ if (pageNo < 1 || pageNo > getNumPages()) {
+ error(errInternal, -1, "Illegal pageNo: {0:d}({1:d})", pageNo, getNumPages() );
+ return errOpenFile;
+ }
+ PDFRectangle *cropBox = NULL;
+ if (getCatalog()->getPage(pageNo)->isCropped()) {
+ cropBox = getCatalog()->getPage(pageNo)->getCropBox();
+ }
+ replacePageDict(pageNo,
+ getCatalog()->getPage(pageNo)->getRotate(),
+ getCatalog()->getPage(pageNo)->getMediaBox(),
+ cropBox, NULL);
+ Ref *refPage = getCatalog()->getPageRef(pageNo);
+ Object page;
+ getXRef()->fetch(refPage->num, refPage->gen, &page);
+
+ if (!(f = fopen(name->getCString(), "wb"))) {
+ error(errIO, -1, "Couldn't open file '{0:t}'", name);
+ return errOpenFile;
+ }
+ outStr = new FileOutStream(f,0);
+
+ yRef = new XRef(getXRef()->getTrailerDict());
+ Object encrypt;
+ getXRef()->getTrailerDict()->dictLookup("Encrypt", &encrypt);
+
+ if (secHdlr != NULL && !secHdlr->isUnencrypted()) {
+ yRef->setEncryption(secHdlr->getPermissionFlags(),
+ secHdlr->getOwnerPasswordOk(), fileKey, keyLength, secHdlr->getEncVersion(), secHdlr->getEncRevision(), encAlgorithm);
+ }
+ countRef = new XRef();
+ Object *trailerObj = getXRef()->getTrailerDict();
+ if (trailerObj->isDict()) {
+ markPageObjects(trailerObj->getDict(), yRef, countRef, 0);
+ }
+ yRef->add(0, 65535, 0, gFalse);
+ writeHeader(outStr, getPDFMajorVersion(), getPDFMinorVersion());
+
+ // get and mark info dict
+ Object infoObj;
+ getXRef()->getDocInfo(&infoObj);
+ if (infoObj.isDict()) {
+ Dict *infoDict = infoObj.getDict();
+ markPageObjects(infoDict, yRef, countRef, 0);
+ if (trailerObj->isDict()) {
+ Dict *trailerDict = trailerObj->getDict();
+ Object ref;
+ trailerDict->lookupNF("Info", &ref);
+ if (ref.isRef()) {
+ yRef->add(ref.getRef().num, ref.getRef().gen, 0, gTrue);
+ if (getXRef()->getEntry(ref.getRef().num)->type == xrefEntryCompressed) {
+ yRef->getEntry(ref.getRef().num)->type = xrefEntryCompressed;
+ }
+ }
+ ref.free();
+ }
+ }
+ infoObj.free();
+
+ // get and mark output intents etc.
+ Object catObj, pagesObj, resourcesObj, annotsObj, afObj;
+ getXRef()->getCatalog(&catObj);
+ Dict *catDict = catObj.getDict();
+ catDict->lookup("Pages", &pagesObj);
+ catDict->lookupNF("AcroForm", &afObj);
+ if (!afObj.isNull()) {
+ markAcroForm(&afObj, yRef, countRef, 0, refPage->num, rootNum + 2);
+ afObj.free();
+ }
+ Dict *pagesDict = pagesObj.getDict();
+ pagesDict->lookup("Resources", &resourcesObj);
+ if (resourcesObj.isDict())
+ markPageObjects(resourcesObj.getDict(), yRef, countRef, 0);
+ markPageObjects(catDict, yRef, countRef, 0);
+
+ Dict *pageDict = page.getDict();
+ markPageObjects(pageDict, yRef, countRef, 0);
+ pageDict->lookupNF("Annots", &annotsObj);
+ if (!annotsObj.isNull()) {
+ markAnnotations(&annotsObj, yRef, countRef, 0, refPage->num, rootNum + 2);
+ annotsObj.free();
+ }
+ yRef->markUnencrypted();
+ Guint objectsCount = writePageObjects(outStr, yRef, 0);
+
+ yRef->add(rootNum,0,outStr->getPos(),gTrue);
+ outStr->printf("%d 0 obj\n", rootNum);
+ outStr->printf("<< /Type /Catalog /Pages %d 0 R", rootNum + 1);
+ for (int j = 0; j < catDict->getLength(); j++) {
+ const char *key = catDict->getKey(j);
+ if (strcmp(key, "Type") != 0 &&
+ strcmp(key, "Catalog") != 0 &&
+ strcmp(key, "Pages") != 0)
+ {
+ if (j > 0) outStr->printf(" ");
+ Object value; catDict->getValNF(j, &value);
+ outStr->printf("/%s ", key);
+ writeObject(&value, outStr, getXRef(), 0, NULL, cryptRC4, 0, 0, 0);
+ value.free();
+ }
+ }
+ catObj.free();
+ pagesObj.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 [ %d 0 R ] /Count 1 ", rootNum + 2);
+ if (resourcesObj.isDict()) {
+ outStr->printf("/Resources ");
+ writeObject(&resourcesObj, outStr, getXRef(), 0, NULL, cryptRC4, 0, 0, 0);
+ resourcesObj.free();
+ }
+ outStr->printf(">>\n");
+ outStr->printf("endobj\n");
+ objectsCount++;
+
+ yRef->add(rootNum + 2,0,outStr->getPos(),gTrue);
+ outStr->printf("%d 0 obj\n", rootNum + 2);
+ outStr->printf("<< ");
+ for (int n = 0; n < pageDict->getLength(); n++) {
+ if (n > 0) outStr->printf(" ");
+ const char *key = pageDict->getKey(n);
+ Object value; pageDict->getValNF(n, &value);
+ if (strcmp(key, "Parent") == 0) {
+ outStr->printf("/Parent %d 0 R", rootNum + 1);
+ } else {
+ outStr->printf("/%s ", key);
+ writeObject(&value, outStr, getXRef(), 0, NULL, cryptRC4, 0, 0, 0);
+ }
+ value.free();
+ }
+ outStr->printf(" >>\nendobj\n");
+ objectsCount++;
+ page.free();
+
+ Goffset uxrefOffset = outStr->getPos();
+ Ref ref;
+ ref.num = rootNum;
+ ref.gen = 0;
+ Dict *trailerDict = createTrailerDict(objectsCount, gFalse, 0, &ref, getXRef(),
+ name->getCString(), uxrefOffset);
+ writeXRefTableTrailer(trailerDict, yRef, gFalse /* do not write unnecessary entries */,
+ uxrefOffset, outStr, getXRef());
+ delete trailerDict;
+
+ outStr->close();
+ fclose(f);
+ delete yRef;
+ delete countRef;
+
+ return errNone;
+}
+
+int PDFDoc::saveAs(GooString *name, PDFWriteMode mode) {
+ FILE *f;
+ OutStream *outStr;
+ int res;
+
+ if (!(f = fopen(name->getCString(), "wb"))) {
+ error(errIO, -1, "Couldn't open file '{0:t}'", name);
+ return errOpenFile;
+ }
+ outStr = new FileOutStream(f,0);
+ res = saveAs(outStr, mode);
+ delete outStr;
+ fclose(f);
+ return res;
+}
+
+int PDFDoc::saveAs(OutStream *outStr, PDFWriteMode mode) {
+
+ // find if we have updated objects
+ GBool updated = gFalse;
+ for(int i=0; i<xref->getNumObjects(); i++) {
+ if (xref->getEntry(i)->getFlag(XRefEntry::Updated)) {
+ updated = gTrue;
+ break;
+ }
+ }
+
+ if (!updated && mode == writeStandard) {
+ // simply copy the original file
+ saveWithoutChangesAs (outStr);
+ } else if (mode == writeForceRewrite) {
+ saveCompleteRewrite(outStr);
+ } else {
+ saveIncrementalUpdate(outStr);
+ }
+
+ return errNone;
+}
+
+int PDFDoc::saveWithoutChangesAs(GooString *name) {
+ FILE *f;
+ OutStream *outStr;
+ int res;
+
+ if (!(f = fopen(name->getCString(), "wb"))) {
+ error(errIO, -1, "Couldn't open file '{0:t}'", name);
+ return errOpenFile;
+ }
+
+ outStr = new FileOutStream(f,0);
+ res = saveWithoutChangesAs(outStr);
+ delete outStr;
+
+ fclose(f);
+
+ return res;
+}
+
+int PDFDoc::saveWithoutChangesAs(OutStream *outStr) {
+ int c;
+
+ BaseStream *copyStr = str->copy();
+ copyStr->reset();
+ while ((c = copyStr->getChar()) != EOF) {
+ outStr->put(c);
+ }
+ copyStr->close();
+ delete copyStr;
+
+ return errNone;
+}
+
+void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
+{
+ XRef *uxref;
+ int c;
+ //copy the original file
+ BaseStream *copyStr = str->copy();
+ copyStr->reset();
+ while ((c = copyStr->getChar()) != EOF) {
+ outStr->put(c);
+ }
+ copyStr->close();
+ delete copyStr;
+
+ Guchar *fileKey;
+ CryptAlgorithm encAlgorithm;
+ int keyLength;
+ xref->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
+ uxref = new XRef();
+ uxref->add(0, 65535, 0, gFalse);
+ xref->lock();
+ for(int i=0; i<xref->getNumObjects(); i++) {
+ if ((xref->getEntry(i)->type == xrefEntryFree) &&
+ (xref->getEntry(i)->gen == 0)) //we skip the irrelevant free objects
+ continue;
+
+ if (xref->getEntry(i)->getFlag(XRefEntry::Updated)) { //we have an updated object
+ Ref ref;
+ ref.num = i;
+ ref.gen = xref->getEntry(i)->type == xrefEntryCompressed ? 0 : xref->getEntry(i)->gen;
+ if (xref->getEntry(i)->type != xrefEntryFree) {
+ Object obj1;
+ xref->fetch(ref.num, ref.gen, &obj1, 1);
+ Goffset offset = writeObjectHeader(&ref, outStr);
+ writeObject(&obj1, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
+ writeObjectFooter(outStr);
+ uxref->add(ref.num, ref.gen, offset, gTrue);
+ obj1.free();
+ } else {
+ uxref->add(ref.num, ref.gen, 0, gFalse);
+ }
+ }
+ }
+ xref->unlock();
+ if (uxref->getNumObjects() == 0) { //we have nothing to update
+ delete uxref;
+ return;
+ }
+
+ Goffset uxrefOffset = outStr->getPos();
+ int numobjects = xref->getNumObjects();
+ const char *fileNameA = fileName ? fileName->getCString() : NULL;
+ Ref rootRef, uxrefStreamRef;
+ rootRef.num = getXRef()->getRootNum();
+ rootRef.gen = getXRef()->getRootGen();
+
+ // Output a xref stream if there is a xref stream already
+ GBool xRefStream = xref->isXRefStream();
+
+ if (xRefStream) {
+ // Append an entry for the xref stream itself
+ uxrefStreamRef.num = numobjects++;
+ uxrefStreamRef.gen = 0;
+ uxref->add(uxrefStreamRef.num, uxrefStreamRef.gen, uxrefOffset, gTrue);
+ }
+
+ Dict *trailerDict = createTrailerDict(numobjects, gTrue, getStartXRef(), &rootRef, getXRef(), fileNameA, uxrefOffset);
+ if (xRefStream) {
+ writeXRefStreamTrailer(trailerDict, uxref, &uxrefStreamRef, uxrefOffset, outStr, getXRef());
+ } else {
+ writeXRefTableTrailer(trailerDict, uxref, gFalse, uxrefOffset, outStr, getXRef());
+ }
+
+ delete trailerDict;
+ delete uxref;
+}
+
+void PDFDoc::saveCompleteRewrite (OutStream* outStr)
+{
+ // Make sure that special flags are set, because we are going to read
+ // all objects, including Unencrypted ones.
+ xref->scanSpecialFlags();
+
+ Guchar *fileKey;
+ CryptAlgorithm encAlgorithm;
+ int keyLength;
+ xref->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
+ outStr->printf("%%PDF-%d.%d\r\n",pdfMajorVersion,pdfMinorVersion);
+ XRef *uxref = new XRef();
+ uxref->add(0, 65535, 0, gFalse);
+ xref->lock();
+ for(int i=0; i<xref->getNumObjects(); i++) {
+ Object obj1;
+ Ref ref;
+ XRefEntryType type = xref->getEntry(i)->type;
+ if (type == xrefEntryFree) {
+ ref.num = i;
+ ref.gen = xref->getEntry(i)->gen;
+ /* the XRef class adds a lot of irrelevant free entries, we only want the significant one
+ and we don't want the one with num=0 because it has already been added (gen = 65535)*/
+ if (ref.gen > 0 && ref.num > 0)
+ uxref->add(ref.num, ref.gen, 0, gFalse);
+ } else if (xref->getEntry(i)->getFlag(XRefEntry::DontRewrite)) {
+ // This entry must not be written, put a free entry instead (with incremented gen)
+ ref.num = i;
+ ref.gen = xref->getEntry(i)->gen + 1;
+ uxref->add(ref.num, ref.gen, 0, gFalse);
+ } else if (type == xrefEntryUncompressed){
+ ref.num = i;
+ ref.gen = xref->getEntry(i)->gen;
+ xref->fetch(ref.num, ref.gen, &obj1, 1);
+ Goffset offset = writeObjectHeader(&ref, outStr);
+ // Write unencrypted objects in unencrypted form
+ if (xref->getEntry(i)->getFlag(XRefEntry::Unencrypted)) {
+ writeObject(&obj1, outStr, NULL, cryptRC4, 0, 0, 0);
+ } else {
+ writeObject(&obj1, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
+ }
+ writeObjectFooter(outStr);
+ uxref->add(ref.num, ref.gen, offset, gTrue);
+ obj1.free();
+ } else if (type == xrefEntryCompressed) {
+ ref.num = i;
+ ref.gen = 0; //compressed entries have gen == 0
+ xref->fetch(ref.num, ref.gen, &obj1, 1);
+ Goffset offset = writeObjectHeader(&ref, outStr);
+ writeObject(&obj1, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
+ writeObjectFooter(outStr);
+ uxref->add(ref.num, ref.gen, offset, gTrue);
+ obj1.free();
+ }
+ }
+ xref->unlock();
+ Goffset uxrefOffset = outStr->getPos();
+ writeXRefTableTrailer(uxrefOffset, uxref, gTrue /* write all entries */,
+ uxref->getNumObjects(), outStr, gFalse /* complete rewrite */);
+ delete uxref;
+}
+
+void PDFDoc::writeDictionnary (Dict* dict, OutStream* outStr, XRef *xRef, Guint numOffset, Guchar *fileKey,
+ CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
+{
+ Object obj1;
+ outStr->printf("<<");
+ for (int i=0; i<dict->getLength(); i++) {
+ GooString keyName(dict->getKey(i));
+ GooString *keyNameToPrint = keyName.sanitizedName(gFalse /* non ps mode */);
+ outStr->printf("/%s ", keyNameToPrint->getCString());
+ delete keyNameToPrint;
+ writeObject(dict->getValNF(i, &obj1), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ obj1.free();
+ }
+ outStr->printf(">> ");
+}
+
+void PDFDoc::writeStream (Stream* str, OutStream* outStr)
+{
+ outStr->printf("stream\r\n");
+ str->reset();
+ for (int c=str->getChar(); c!= EOF; c=str->getChar()) {
+ outStr->printf("%c", c);
+ }
+ outStr->printf("\r\nendstream\r\n");
+}
+
+void PDFDoc::writeRawStream (Stream* str, OutStream* outStr)
+{
+ Object obj1;
+ str->getDict()->lookup("Length", &obj1);
+ if (!obj1.isInt() && !obj1.isInt64()) {
+ error (errSyntaxError, -1, "PDFDoc::writeRawStream, no Length in stream dict");
+ return;
+ }
+
+ Goffset length;
+ if (obj1.isInt())
+ length = obj1.getInt();
+ else
+ length = obj1.getInt64();
+ obj1.free();
+
+ outStr->printf("stream\r\n");
+ str->unfilteredReset();
+ for (Goffset i = 0; i < length; i++) {
+ int c = str->getUnfilteredChar();
+ outStr->printf("%c", c);
+ }
+ str->reset();
+ outStr->printf("\r\nendstream\r\n");
+}
+
+void PDFDoc::writeString (GooString* s, OutStream* outStr, Guchar *fileKey,
+ CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
+{
+ // Encrypt string if encryption is enabled
+ GooString *sEnc = NULL;
+ if (fileKey) {
+ Object obj;
+ EncryptStream *enc = new EncryptStream(new MemStream(s->getCString(), 0, s->getLength(), obj.initNull()),
+ fileKey, encAlgorithm, keyLength, objNum, objGen);
+ sEnc = new GooString();
+ int c;
+ enc->reset();
+ while ((c = enc->getChar()) != EOF) {
+ sEnc->append((char)c);
+ }
+
+ delete enc;
+ s = sEnc;
+ }
+
+ // Write data
+ if (s->hasUnicodeMarker()) {
+ //unicode string don't necessary end with \0
+ const char* c = s->getCString();
+ outStr->printf("(");
+ for(int i=0; i<s->getLength(); i++) {
+ char unescaped = *(c+i)&0x000000ff;
+ //escape if needed
+ if (unescaped == '(' || unescaped == ')' || unescaped == '\\')
+ outStr->printf("%c", '\\');
+ outStr->printf("%c", unescaped);
+ }
+ outStr->printf(") ");
+ } else {
+ const char* c = s->getCString();
+ outStr->printf("(");
+ for(int i=0; i<s->getLength(); i++) {
+ char unescaped = *(c+i)&0x000000ff;
+ //escape if needed
+ if (unescaped == '\r')
+ outStr->printf("\\r");
+ else if (unescaped == '\n')
+ outStr->printf("\\n");
+ else {
+ if (unescaped == '(' || unescaped == ')' || unescaped == '\\') {
+ outStr->printf("%c", '\\');
+ }
+ outStr->printf("%c", unescaped);
+ }
+ }
+ outStr->printf(") ");
+ }
+
+ delete sEnc;
+}
+
+Goffset PDFDoc::writeObjectHeader (Ref *ref, OutStream* outStr)
+{
+ Goffset offset = outStr->getPos();
+ outStr->printf("%i %i obj ", ref->num, ref->gen);
+ return offset;
+}
+
+void PDFDoc::writeObject (Object* obj, OutStream* outStr, XRef *xRef, Guint numOffset, Guchar *fileKey,
+ CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen)
+{
+ Array *array;
+ Object obj1;
+ Goffset tmp;
+
+ switch (obj->getType()) {
+ case objBool:
+ outStr->printf("%s ", obj->getBool()?"true":"false");
+ break;
+ case objInt:
+ outStr->printf("%i ", obj->getInt());
+ break;
+ case objInt64:
+ outStr->printf("%lli ", obj->getInt64());
+ break;
+ case objReal:
+ {
+ GooString s;
+ s.appendf("{0:.10g}", obj->getReal());
+ outStr->printf("%s ", s.getCString());
+ break;
+ }
+ case objString:
+ writeString(obj->getString(), outStr, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ break;
+ case objName:
+ {
+ GooString name(obj->getName());
+ GooString *nameToPrint = name.sanitizedName(gFalse /* non ps mode */);
+ outStr->printf("/%s ", nameToPrint->getCString());
+ delete nameToPrint;
+ break;
+ }
+ case objNull:
+ outStr->printf( "null ");
+ break;
+ case objArray:
+ array = obj->getArray();
+ outStr->printf("[");
+ for (int i=0; i<array->getLength(); i++) {
+ writeObject(array->getNF(i, &obj1), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ obj1.free();
+ }
+ outStr->printf("] ");
+ break;
+ case objDict:
+ writeDictionnary (obj->getDict(), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ break;
+ case objStream:
+ {
+ //We can't modify stream with the current implementation (no write functions in Stream API)
+ // => the only type of streams which that have been modified are internal streams (=strWeird)
+ Stream *stream = obj->getStream();
+ if (stream->getKind() == strWeird || stream->getKind() == strCrypt) {
+ //we write the stream unencoded => TODO: write stream encoder
+
+ // Encrypt stream
+ EncryptStream *encStream = NULL;
+ GBool removeFilter = gTrue;
+ if (stream->getKind() == strWeird && fileKey) {
+ Object filter;
+ stream->getDict()->lookup("Filter", &filter);
+ if (!filter.isName("Crypt")) {
+ if (filter.isArray()) {
+ for (int i = 0; i < filter.arrayGetLength(); i++) {
+ Object filterEle;
+ filter.arrayGet(i, &filterEle);
+ if (filterEle.isName("Crypt")) {
+ filterEle.free();
+ removeFilter = gFalse;
+ break;
+ }
+ filterEle.free();
+ }
+ if (removeFilter) {
+ encStream = new EncryptStream(stream, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ encStream->setAutoDelete(gFalse);
+ stream = encStream;
+ }
+ } else {
+ encStream = new EncryptStream(stream, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ encStream->setAutoDelete(gFalse);
+ stream = encStream;
+ }
+ } else {
+ removeFilter = gFalse;
+ }
+ filter.free();
+ } else if (fileKey != NULL) { // Encrypt stream
+ encStream = new EncryptStream(stream, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ encStream->setAutoDelete(gFalse);
+ stream = encStream;
+ }
+
+ stream->reset();
+ //recalculate stream length
+ tmp = 0;
+ for (int c=stream->getChar(); c!=EOF; c=stream->getChar()) {
+ tmp++;
+ }
+ obj1.initInt64(tmp);
+ stream->getDict()->set("Length", &obj1);
+
+ //Remove Stream encoding
+ if (removeFilter) {
+ stream->getDict()->remove("Filter");
+ }
+ stream->getDict()->remove("DecodeParms");
+
+ writeDictionnary (stream->getDict(),outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ writeStream (stream,outStr);
+ delete encStream;
+ obj1.free();
+ } else {
+ //raw stream copy
+ FilterStream *fs = dynamic_cast<FilterStream*>(stream);
+ if (fs) {
+ BaseStream *bs = fs->getBaseStream();
+ if (bs) {
+ Goffset streamEnd;
+ if (xRef->getStreamEnd(bs->getStart(), &streamEnd)) {
+ Object val;
+ val.initInt64(streamEnd - bs->getStart());
+ stream->getDict()->set("Length", &val);
+ }
+ }
+ }
+ writeDictionnary (stream->getDict(), outStr, xRef, numOffset, fileKey, encAlgorithm, keyLength, objNum, objGen);
+ writeRawStream (stream, outStr);
+ }
+ break;
+ }
+ case objRef:
+ outStr->printf("%i %i R ", obj->getRef().num + numOffset, obj->getRef().gen);
+ break;
+ case objCmd:
+ outStr->printf("%s\n", obj->getCmd());
+ break;
+ case objError:
+ outStr->printf("error\r\n");
+ break;
+ case objEOF:
+ outStr->printf("eof\r\n");
+ break;
+ case objNone:
+ outStr->printf("none\r\n");
+ break;
+ default:
+ error(errUnimplemented, -1,"Unhandled objType : {0:d}, please report a bug with a testcase\r\n", obj->getType());
+ break;
+ }
+}
+
+void PDFDoc::writeObjectFooter (OutStream* outStr)
+{
+ outStr->printf("endobj\r\n");
+}
+
+Dict *PDFDoc::createTrailerDict(int uxrefSize, GBool incrUpdate, Goffset startxRef,
+ Ref *root, XRef *xRef, const char *fileName, Goffset fileSize)
+{
+ Dict *trailerDict = new Dict(xRef);
+ Object obj1;
+ obj1.initInt(uxrefSize);
+ trailerDict->set("Size", &obj1);
+ obj1.free();
+
+ //build a new ID, as recommended in the reference, uses:
+ // - current time
+ // - file name
+ // - file size
+ // - values of entry in information dictionnary
+ GooString message;
+ char buffer[256];
+ sprintf(buffer, "%i", (int)time(NULL));
+ message.append(buffer);
+
+ if (fileName)
+ message.append(fileName);
+
+ sprintf(buffer, "%lli", (long long)fileSize);
+ message.append(buffer);
+
+ //info dict -- only use text string
+ if (!xRef->getTrailerDict()->isNone() && xRef->getDocInfo(&obj1)->isDict()) {
+ for(int i=0; i<obj1.getDict()->getLength(); i++) {
+ Object obj2;
+ obj1.getDict()->getVal(i, &obj2);
+ if (obj2.isString()) {
+ message.append(obj2.getString());
+ }
+ obj2.free();
+ }
+ }
+ obj1.free();
+
+ GBool hasEncrypt = gFalse;
+ if (!xRef->getTrailerDict()->isNone()) {
+ Object obj2;
+ xRef->getTrailerDict()->dictLookupNF("Encrypt", &obj2);
+ if (!obj2.isNull()) {
+ trailerDict->set("Encrypt", &obj2);
+ hasEncrypt = gTrue;
+ obj2.free();
+ }
+ }
+
+ //calculate md5 digest
+ Guchar digest[16];
+ md5((Guchar*)message.getCString(), message.getLength(), digest);
+ obj1.initString(new GooString((const char*)digest, 16));
+
+ //create ID array
+ Object obj2,obj3,obj5;
+ obj2.initArray(xRef);
+
+ // In case of encrypted files, the ID must not be changed because it's used to calculate the key
+ if (incrUpdate || hasEncrypt) {
+ Object obj4;
+ //only update the second part of the array
+ xRef->getTrailerDict()->getDict()->lookup("ID", &obj4);
+ if (!obj4.isArray()) {
+ error(errSyntaxWarning, -1, "PDFDoc::createTrailerDict original file's ID entry isn't an array. Trying to continue");
+ } else {
+ //Get the first part of the ID
+ obj4.arrayGet(0,&obj3);
+
+ obj2.arrayAdd(&obj3);
+ obj2.arrayAdd(&obj1);
+ trailerDict->set("ID", &obj2);
+ }
+ obj4.free();
+ } else {
+ //new file => same values for the two identifiers
+ obj2.arrayAdd(&obj1);
+ obj1.initString(new GooString((const char*)digest, 16));
+ obj2.arrayAdd(&obj1);
+ trailerDict->set("ID", &obj2);
+ }
+
+ obj1.initRef(root->num, root->gen);
+ trailerDict->set("Root", &obj1);
+
+ if (incrUpdate) {
+ obj1.initInt64(startxRef);
+ trailerDict->set("Prev", &obj1);
+ }
+
+ if (!xRef->getTrailerDict()->isNone()) {
+ xRef->getDocInfoNF(&obj5);
+ if (!obj5.isNull()) {
+ trailerDict->set("Info", &obj5);
+ }
+ }
+
+ return trailerDict;
+}
+
+void PDFDoc::writeXRefTableTrailer(Dict *trailerDict, XRef *uxref, GBool writeAllEntries, Goffset uxrefOffset, OutStream* outStr, XRef *xRef)
+{
+ uxref->writeTableToFile( outStr, writeAllEntries );
+ outStr->printf( "trailer\r\n");
+ writeDictionnary(trailerDict, outStr, xRef, 0, NULL, cryptRC4, 0, 0, 0);
+ outStr->printf( "\r\nstartxref\r\n");
+ outStr->printf( "%lli\r\n", uxrefOffset);
+ outStr->printf( "%%%%EOF\r\n");
+}
+
+void PDFDoc::writeXRefStreamTrailer (Dict *trailerDict, XRef *uxref, Ref *uxrefStreamRef, Goffset uxrefOffset, OutStream* outStr, XRef *xRef)
+{
+ GooString stmData;
+
+ // Fill stmData and some trailerDict fields
+ uxref->writeStreamToBuffer(&stmData, trailerDict, xRef);
+
+ // Create XRef stream object and write it
+ Object obj1;
+ MemStream *mStream = new MemStream( stmData.getCString(), 0,
+ stmData.getLength(), obj1.initDict(trailerDict) );
+ writeObjectHeader(uxrefStreamRef, outStr);
+ writeObject(obj1.initStream(mStream), outStr, xRef, 0, NULL, cryptRC4, 0, 0, 0);
+ writeObjectFooter(outStr);
+ obj1.free();
+
+ outStr->printf( "startxref\r\n");
+ outStr->printf( "%lli\r\n", uxrefOffset);
+ outStr->printf( "%%%%EOF\r\n");
+}
+
+void PDFDoc::writeXRefTableTrailer(Goffset uxrefOffset, XRef *uxref, GBool writeAllEntries,
+ int uxrefSize, OutStream* outStr, GBool incrUpdate)
+{
+ const char *fileNameA = fileName ? fileName->getCString() : NULL;
+ // file size (doesn't include the trailer)
+ unsigned int fileSize = 0;
+ int c;
+ str->reset();
+ while ((c = str->getChar()) != EOF) {
+ fileSize++;
+ }
+ str->close();
+ Ref ref;
+ ref.num = getXRef()->getRootNum();
+ ref.gen = getXRef()->getRootGen();
+ Dict * trailerDict = createTrailerDict(uxrefSize, incrUpdate, getStartXRef(), &ref,
+ getXRef(), fileNameA, fileSize);
+ writeXRefTableTrailer(trailerDict, uxref, writeAllEntries, uxrefOffset, outStr, getXRef());
+ delete trailerDict;
+}
+
+void PDFDoc::writeHeader(OutStream *outStr, int major, int minor)
+{
+ outStr->printf("%%PDF-%d.%d\n", major, minor);
+ outStr->printf("%%\xE2\xE3\xCF\xD3\n");
+}
+
+void PDFDoc::markDictionnary (Dict* dict, XRef * xRef, XRef *countRef, Guint numOffset)
+{
+ Object obj1;
+ for (int i=0; i<dict->getLength(); i++) {
+ markObject(dict->getValNF(i, &obj1), xRef, countRef, numOffset);
+ obj1.free();
+ }
+}
+
+void PDFDoc::markObject (Object* obj, XRef *xRef, XRef *countRef, Guint numOffset)
+{
+ Array *array;
+ Object obj1;
+
+ switch (obj->getType()) {
+ case objArray:
+ array = obj->getArray();
+ for (int i=0; i<array->getLength(); i++) {
+ markObject(array->getNF(i, &obj1), xRef, countRef, numOffset);
+ obj1.free();
+ }
+ break;
+ case objDict:
+ markDictionnary (obj->getDict(), xRef, countRef, numOffset);
+ break;
+ case objStream:
+ {
+ Stream *stream = obj->getStream();
+ markDictionnary (stream->getDict(), xRef, countRef, numOffset);
+ }
+ break;
+ case objRef:
+ {
+ if (obj->getRef().num + (int) numOffset >= xRef->getNumObjects() || xRef->getEntry(obj->getRef().num + numOffset)->type == xrefEntryFree) {
+ if (getXRef()->getEntry(obj->getRef().num)->type == xrefEntryFree) {
+ return; // already marked as free => should be replaced
+ }
+ xRef->add(obj->getRef().num + numOffset, obj->getRef().gen, 0, gTrue);
+ if (getXRef()->getEntry(obj->getRef().num)->type == xrefEntryCompressed) {
+ xRef->getEntry(obj->getRef().num + numOffset)->type = xrefEntryCompressed;
+ }
+ }
+ if (obj->getRef().num + (int) numOffset >= countRef->getNumObjects() ||
+ countRef->getEntry(obj->getRef().num + numOffset)->type == xrefEntryFree)
+ {
+ countRef->add(obj->getRef().num + numOffset, 1, 0, gTrue);
+ } else {
+ XRefEntry *entry = countRef->getEntry(obj->getRef().num + numOffset);
+ entry->gen++;
+ if (entry->gen > 9)
+ break;
+ }
+ Object obj1;
+ getXRef()->fetch(obj->getRef().num, obj->getRef().gen, &obj1);
+ markObject(&obj1, xRef, countRef, numOffset);
+ obj1.free();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void PDFDoc::replacePageDict(int pageNo, int rotate,
+ PDFRectangle *mediaBox,
+ PDFRectangle *cropBox, Object *pageCTM)
+{
+ Ref *refPage = getCatalog()->getPageRef(pageNo);
+ Object page;
+ getXRef()->fetch(refPage->num, refPage->gen, &page);
+ Dict *pageDict = page.getDict();
+ pageDict->remove("MediaBox");
+ pageDict->remove("CropBox");
+ pageDict->remove("ArtBox");
+ pageDict->remove("BleedBox");
+ pageDict->remove("TrimBox");
+ pageDict->remove("Rotate");
+ Object *mediaBoxObj = new Object();
+ mediaBoxObj->initArray(getXRef());
+ Object *murx = new Object();
+ murx->initReal(mediaBox->x1);
+ Object *mury = new Object();
+ mury->initReal(mediaBox->y1);
+ Object *mllx = new Object();
+ mllx->initReal(mediaBox->x2);
+ Object *mlly = new Object();
+ mlly->initReal(mediaBox->y2);
+ mediaBoxObj->arrayAdd(murx);
+ mediaBoxObj->arrayAdd(mury);
+ mediaBoxObj->arrayAdd(mllx);
+ mediaBoxObj->arrayAdd(mlly);
+ pageDict->add(copyString("MediaBox"), mediaBoxObj);
+ if (cropBox != NULL) {
+ Object *cropBoxObj = new Object();
+ cropBoxObj->initArray(getXRef());
+ Object *curx = new Object();
+ curx->initReal(cropBox->x1);
+ Object *cury = new Object();
+ cury->initReal(cropBox->y1);
+ Object *cllx = new Object();
+ cllx->initReal(cropBox->x2);
+ Object *clly = new Object();
+ clly->initReal(cropBox->y2);
+ cropBoxObj->arrayAdd(curx);
+ cropBoxObj->arrayAdd(cury);
+ cropBoxObj->arrayAdd(cllx);
+ cropBoxObj->arrayAdd(clly);
+ pageDict->add(copyString("CropBox"), cropBoxObj);
+ pageDict->add(copyString("TrimBox"), cropBoxObj);
+ } else {
+ pageDict->add(copyString("TrimBox"), mediaBoxObj);
+ }
+ Object *rotateObj = new Object();
+ rotateObj->initInt(rotate);
+ pageDict->add(copyString("Rotate"), rotateObj);
+ if (pageCTM != NULL) {
+ Object *contents = new Object();
+ Ref cmRef = getXRef()->addIndirectObject(pageCTM);
+ Object *ref = new Object();
+ ref->initRef(cmRef.num, cmRef.gen);
+ pageDict->lookupNF("Contents", contents);
+ Object *newContents = new Object();
+ newContents->initArray(getXRef());
+ if (contents->getType() == objRef) {
+ newContents->arrayAdd(ref);
+ newContents->arrayAdd(contents);
+ } else {
+ newContents->arrayAdd(ref);
+ for (int i = 0; i < contents->arrayGetLength(); i++) {
+ Object *contentEle = new Object();
+ contents->arrayGetNF(i, contentEle);
+ newContents->arrayAdd(contentEle);
+ }
+ }
+ pageDict->remove("Contents");
+ pageDict->add(copyString("Contents"), newContents);
+ }
+ getXRef()->setModifiedObject(&page, *refPage);
+ page.free();
+}
+
+void PDFDoc::markPageObjects(Dict *pageDict, XRef *xRef, XRef *countRef, Guint numOffset)
+{
+ pageDict->remove("Names");
+ pageDict->remove("OpenAction");
+ pageDict->remove("Outlines");
+ pageDict->remove("StructTreeRoot");
+
+ for (int n = 0; n < pageDict->getLength(); n++) {
+ const char *key = pageDict->getKey(n);
+ Object value; pageDict->getValNF(n, &value);
+ if (strcmp(key, "Parent") != 0 &&
+ strcmp(key, "Pages") != 0 &&
+ strcmp(key, "AcroForm") != 0 &&
+ strcmp(key, "Annots") != 0 &&
+ strcmp(key, "P") != 0 &&
+ strcmp(key, "Root") != 0) {
+ markObject(&value, xRef, countRef, numOffset);
+ }
+ value.free();
+ }
+}
+
+GBool PDFDoc::markAnnotations(Object *annotsObj, XRef *xRef, XRef *countRef, Guint numOffset, Guint oldPageNum, Guint newPageNum) {
+ Object annots;
+ GBool modified = gFalse;
+ annotsObj->fetch(getXRef(), &annots);
+ if (annots.isArray()) {
+ Array *array = annots.getArray();
+ for (int i=array->getLength() - 1; i >= 0; i--) {
+ Object obj1;
+ if (array->get(i, &obj1)->isDict()) {
+ Object type;
+ Dict *dict = obj1.getDict();
+ dict->lookup("Type", &type);
+ if (type.isName() && strcmp(type.getName(), "Annot") == 0) {
+ Object obj2;
+ if (dict->lookupNF("P", &obj2)->isRef()) {
+ if (obj2.getRef().num == oldPageNum) {
+ Object obj3;
+ array->getNF(i, &obj3);
+ if (obj3.isRef()) {
+ Object *newRef = new Object();
+ newRef->initRef(newPageNum, 0);
+ dict->set("P", newRef);
+ getXRef()->setModifiedObject(&obj1, obj3.getRef());
+ }
+ obj3.free();
+ } else if (obj2.getRef().num == newPageNum) {
+ obj1.free();
+ obj2.free();
+ type.free();
+ continue;
+ } else {
+ array->remove(i);
+ obj1.free();
+ obj2.free();
+ type.free();
+ modified = gTrue;
+ continue;
+ }
+ }
+ obj2.free();
+ }
+ type.free();
+ markPageObjects(dict, xRef, countRef, numOffset);
+ }
+ obj1.free();
+ array->getNF(i, &obj1);
+ if (obj1.isRef()) {
+ if (obj1.getRef().num + (int) numOffset >= xRef->getNumObjects() || xRef->getEntry(obj1.getRef().num + numOffset)->type == xrefEntryFree) {
+ if (getXRef()->getEntry(obj1.getRef().num)->type == xrefEntryFree) {
+ continue; // already marked as free => should be replaced
+ }
+ xRef->add(obj1.getRef().num + numOffset, obj1.getRef().gen, 0, gTrue);
+ if (getXRef()->getEntry(obj1.getRef().num)->type == xrefEntryCompressed) {
+ xRef->getEntry(obj1.getRef().num + numOffset)->type = xrefEntryCompressed;
+ }
+ }
+ if (obj1.getRef().num + (int) numOffset >= countRef->getNumObjects() ||
+ countRef->getEntry(obj1.getRef().num + numOffset)->type == xrefEntryFree)
+ {
+ countRef->add(obj1.getRef().num + numOffset, 1, 0, gTrue);
+ } else {
+ XRefEntry *entry = countRef->getEntry(obj1.getRef().num + numOffset);
+ entry->gen++;
+ }
+ }
+ obj1.free();
+ }
+ }
+ if (annotsObj->isRef()) {
+ if (annotsObj->getRef().num + (int) numOffset >= xRef->getNumObjects() || xRef->getEntry(annotsObj->getRef().num + numOffset)->type == xrefEntryFree) {
+ if (getXRef()->getEntry(annotsObj->getRef().num)->type == xrefEntryFree) {
+ return modified; // already marked as free => should be replaced
+ }
+ xRef->add(annotsObj->getRef().num + numOffset, annotsObj->getRef().gen, 0, gTrue);
+ if (getXRef()->getEntry(annotsObj->getRef().num)->type == xrefEntryCompressed) {
+ xRef->getEntry(annotsObj->getRef().num + numOffset)->type = xrefEntryCompressed;
+ }
+ }
+ if (annotsObj->getRef().num + (int) numOffset >= countRef->getNumObjects() ||
+ countRef->getEntry(annotsObj->getRef().num + numOffset)->type == xrefEntryFree)
+ {
+ countRef->add(annotsObj->getRef().num + numOffset, 1, 0, gTrue);
+ } else {
+ XRefEntry *entry = countRef->getEntry(annotsObj->getRef().num + numOffset);
+ entry->gen++;
+ }
+ getXRef()->setModifiedObject(&annots, annotsObj->getRef());
+ }
+ annots.free();
+ return modified;
+}
+
+void PDFDoc::markAcroForm(Object *afObj, XRef *xRef, XRef *countRef, Guint numOffset, Guint oldPageNum, Guint newPageNum) {
+ Object acroform;
+ GBool modified = gFalse;
+ afObj->fetch(getXRef(), &acroform);
+ if (acroform.isDict()) {
+ Dict *dict = acroform.getDict();
+ for (int i=0; i < dict->getLength(); i++) {
+ if (strcmp(dict->getKey(i), "Fields") == 0) {
+ Object fields;
+ modified = markAnnotations(dict->getValNF(i, &fields), xRef, countRef, numOffset, oldPageNum, newPageNum);
+ fields.free();
+ } else {
+ Object obj;
+ markObject(dict->getValNF(i, &obj), xRef, countRef, numOffset);
+ obj.free();
+ }
+ }
+ }
+ if (afObj->isRef()) {
+ if (afObj->getRef().num + (int) numOffset >= xRef->getNumObjects() || xRef->getEntry(afObj->getRef().num + numOffset)->type == xrefEntryFree) {
+ if (getXRef()->getEntry(afObj->getRef().num)->type == xrefEntryFree) {
+ return; // already marked as free => should be replaced
+ }
+ xRef->add(afObj->getRef().num + numOffset, afObj->getRef().gen, 0, gTrue);
+ if (getXRef()->getEntry(afObj->getRef().num)->type == xrefEntryCompressed) {
+ xRef->getEntry(afObj->getRef().num + numOffset)->type = xrefEntryCompressed;
+ }
+ }
+ if (afObj->getRef().num + (int) numOffset >= countRef->getNumObjects() ||
+ countRef->getEntry(afObj->getRef().num + numOffset)->type == xrefEntryFree)
+ {
+ countRef->add(afObj->getRef().num + numOffset, 1, 0, gTrue);
+ } else {
+ XRefEntry *entry = countRef->getEntry(afObj->getRef().num + numOffset);
+ entry->gen++;
+ }
+ if (modified){
+ getXRef()->setModifiedObject(&acroform, afObj->getRef());
+ }
+ }
+ acroform.free();
+ return;
+}
+
+Guint PDFDoc::writePageObjects(OutStream *outStr, XRef *xRef, Guint numOffset, GBool combine)
+{
+ Guint objectsCount = 0; //count the number of objects in the XRef(s)
+ Guchar *fileKey;
+ CryptAlgorithm encAlgorithm;
+ int keyLength;
+ xRef->getEncryptionParameters(&fileKey, &encAlgorithm, &keyLength);
+
+ for (int n = numOffset; n < xRef->getNumObjects(); n++) {
+ if (xRef->getEntry(n)->type != xrefEntryFree) {
+ Object obj;
+ Ref ref;
+ ref.num = n;
+ ref.gen = xRef->getEntry(n)->gen;
+ objectsCount++;
+ getXRef()->fetch(ref.num - numOffset, ref.gen, &obj);
+ Goffset offset = writeObjectHeader(&ref, outStr);
+ if (combine) {
+ writeObject(&obj, outStr, getXRef(), numOffset, NULL, cryptRC4, 0, 0, 0);
+ } else if (xRef->getEntry(n)->getFlag(XRefEntry::Unencrypted)) {
+ writeObject(&obj, outStr, NULL, cryptRC4, 0, 0, 0);
+ } else {
+ writeObject(&obj, outStr, fileKey, encAlgorithm, keyLength, ref.num, ref.gen);
+ }
+ writeObjectFooter(outStr);
+ xRef->add(ref.num, ref.gen, offset, gTrue);
+ obj.free();
+ }
+ }
+ return objectsCount;
+}
+
+#ifndef DISABLE_OUTLINE
+Outline *PDFDoc::getOutline()
+{
+ if (!outline) {
+ pdfdocLocker();
+ // read outline
+ outline = new Outline(catalog->getOutline(), xref);
+ }
+
+ return outline;
+}
+#endif
+
+PDFDoc *PDFDoc::ErrorPDFDoc(int errorCode, GooString *fileNameA)
+{
+ PDFDoc *doc = new PDFDoc();
+ doc->errCode = errorCode;
+ doc->fileName = fileNameA;
+
+ return doc;
+}
+
+long long PDFDoc::strToLongLong(char *s) {
+ long long x, d;
+ char *p;
+
+ x = 0;
+ for (p = s; *p && isdigit(*p & 0xff); ++p) {
+ d = *p - '0';
+ if (x > (LLONG_MAX - d) / 10) {
+ break;
+ }
+ x = 10 * x + d;
+ }
+ return x;
+}
+
+// Read the 'startxref' position.
+Goffset PDFDoc::getStartXRef(GBool tryingToReconstruct)
+{
+ if (startXRefPos == -1) {
+
+ if (isLinearized(tryingToReconstruct)) {
+ char buf[linearizationSearchSize+1];
+ int c, n, i;
+
+ str->setPos(0);
+ for (n = 0; n < linearizationSearchSize; ++n) {
+ if ((c = str->getChar()) == EOF) {
+ break;
+ }
+ buf[n] = c;
+ }
+ buf[n] = '\0';
+
+ // find end of first obj (linearization dictionary)
+ startXRefPos = 0;
+ for (i = 0; i < n; i++) {
+ if (!strncmp("endobj", &buf[i], 6)) {
+ i += 6;
+ //skip whitespace
+ while (buf[i] && Lexer::isSpace(buf[i])) ++i;
+ startXRefPos = i;
+ break;
+ }
+ }
+ } else {
+ char buf[xrefSearchSize+1];
+ char *p;
+ int c, n, i;
+
+ // read last xrefSearchSize bytes
+ str->setPos(xrefSearchSize, -1);
+ for (n = 0; n < xrefSearchSize; ++n) {
+ if ((c = str->getChar()) == EOF) {
+ break;
+ }
+ buf[n] = c;
+ }
+ buf[n] = '\0';
+
+ // find startxref
+ for (i = n - 9; i >= 0; --i) {
+ if (!strncmp(&buf[i], "startxref", 9)) {
+ break;
+ }
+ }
+ if (i < 0) {
+ startXRefPos = 0;
+ } else {
+ for (p = &buf[i+9]; isspace(*p); ++p) ;
+ startXRefPos = strToLongLong(p);
+ }
+ }
+
+ }
+
+ return startXRefPos;
+}
+
+Goffset PDFDoc::getMainXRefEntriesOffset(GBool tryingToReconstruct)
+{
+ Guint mainXRefEntriesOffset = 0;
+
+ if (isLinearized(tryingToReconstruct)) {
+ mainXRefEntriesOffset = getLinearization()->getMainXRefEntriesOffset();
+ }
+
+ return mainXRefEntriesOffset;
+}
+
+int PDFDoc::getNumPages()
+{
+ if (isLinearized()) {
+ int n;
+ if ((n = getLinearization()->getNumPages())) {
+ return n;
+ }
+ }
+
+ return catalog->getNumPages();
+}
+
+Page *PDFDoc::parsePage(int page)
+{
+ Page *p = NULL;
+ Object obj;
+ Ref pageRef;
+ Dict *pageDict;
+
+ pageRef.num = getHints()->getPageObjectNum(page);
+ if (!pageRef.num) {
+ error(errSyntaxWarning, -1, "Failed to get object num from hint tables for page {0:d}", page);
+ return NULL;
+ }
+
+ // check for bogus ref - this can happen in corrupted PDF files
+ if (pageRef.num < 0 || pageRef.num >= xref->getNumObjects()) {
+ error(errSyntaxWarning, -1, "Invalid object num ({0:d}) for page {1:d}", pageRef.num, page);
+ return NULL;
+ }
+
+ pageRef.gen = xref->getEntry(pageRef.num)->gen;
+ xref->fetch(pageRef.num, pageRef.gen, &obj);
+ if (!obj.isDict("Page")) {
+ obj.free();
+ error(errSyntaxWarning, -1, "Object ({0:d} {1:d}) is not a pageDict", pageRef.num, pageRef.gen);
+ return NULL;
+ }
+ pageDict = obj.getDict();
+
+ p = new Page(this, page, pageDict, pageRef,
+ new PageAttrs(NULL, pageDict), catalog->getForm());
+ obj.free();
+
+ return p;
+}
+
+Page *PDFDoc::getPage(int page)
+{
+ if ((page < 1) || page > getNumPages()) return NULL;
+
+ if (isLinearized()) {
+ pdfdocLocker();
+ if (!pageCache) {
+ pageCache = (Page **) gmallocn(getNumPages(), sizeof(Page *));
+ for (int i = 0; i < getNumPages(); i++) {
+ pageCache[i] = NULL;
+ }
+ }
+ if (!pageCache[page-1]) {
+ pageCache[page-1] = parsePage(page);
+ }
+ if (pageCache[page-1]) {
+ return pageCache[page-1];
+ } else {
+ error(errSyntaxWarning, -1, "Failed parsing page {0:d} using hint tables", page);
+ }
+ }
+
+ return catalog->getPage(page);
+}