[poppler] 11 commits - poppler/Annot.cc poppler/Annot.h poppler/Decrypt.cc poppler/Decrypt.h poppler/Dict.cc poppler/Dict.h poppler/Form.cc poppler/PDFDoc.cc poppler/PDFDoc.h poppler/Stream.cc poppler/Stream.h poppler/XRef.cc poppler/XRef.h test/Makefile.am test/pdf-fullrewrite.cc

Albert Astals Cid aacid at kemper.freedesktop.org
Sat Jan 19 04:39:00 PST 2008


 poppler/Annot.cc        |   42 ++++-
 poppler/Annot.h         |    1 
 poppler/Decrypt.cc      |    5 
 poppler/Decrypt.h       |    1 
 poppler/Dict.cc         |   14 +
 poppler/Dict.h          |    1 
 poppler/Form.cc         |    7 
 poppler/PDFDoc.cc       |  379 +++++++++++++++++++++++++++++++++++++++++++++++-
 poppler/PDFDoc.h        |   18 ++
 poppler/Stream.cc       |   96 ++++++++++--
 poppler/Stream.h        |   94 +++++++++++
 poppler/XRef.cc         |   55 ++++++
 poppler/XRef.h          |    6 
 test/Makefile.am        |   11 +
 test/pdf-fullrewrite.cc |   44 +++++
 15 files changed, 738 insertions(+), 36 deletions(-)

New commits:
commit a20468cc38bb590124b2b028a08f364f90f2487f
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 13:02:17 2008 +0100

    Adds a test application to test full rewrite functionnality.

diff --git a/test/Makefile.am b/test/Makefile.am
index df1809a..d46b988 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -33,6 +33,9 @@ perf_test =				\
 
 endif
 
+pdf_fullrewrite = \
+	pdf-fullrewrite
+
 INCLUDES =					\
 	-I$(top_srcdir)				\
 	-I$(top_srcdir)/poppler			\
@@ -42,7 +45,7 @@ INCLUDES =					\
 	$(GTK_TEST_CFLAGS)			\
 	$(FONTCONFIG_CFLAGS)
 
-noinst_PROGRAMS = $(gtk_splash_test) $(gtk_cairo_test) $(pdf_inspector) $(perf_test)
+noinst_PROGRAMS = $(gtk_splash_test) $(gtk_cairo_test) $(pdf_inspector) $(perf_test) $(pdf_fullrewrite)
 
 gtk_splash_test_SOURCES =			\
        gtk-splash-test.cc
@@ -79,5 +82,11 @@ perf_test_LDADD =				\
 	$(top_builddir)/poppler/libpoppler.la	\
 	$(FREETYPE_LIBS)
 
+pdf_fullrewrite_SOURCES = \
+	pdf-fullrewrite.cc
+
+pdf_fullrewrite_LDADD = \
+	$(top_builddir)/poppler/libpoppler.la
+
 EXTRA_DIST =					\
 	pdf-operators.c
diff --git a/test/pdf-fullrewrite.cc b/test/pdf-fullrewrite.cc
new file mode 100644
index 0000000..b782bc4
--- /dev/null
+++ b/test/pdf-fullrewrite.cc
@@ -0,0 +1,44 @@
+//========================================================================
+//
+// pdf-fullrewrite.cc
+//
+// Copyright 2007 Julien Rebetez
+//
+//========================================================================
+#include "config.h"
+#include <poppler-config.h>
+#include "GlobalParams.h"
+#include "Error.h"
+#include "PDFDoc.h"
+#include "goo/GooString.h"
+
+int main (int argc, char *argv[])
+{
+  PDFDoc *doc;
+  GooString *inputName, *outputName;
+
+  // parse args
+  if (argc < 3) {
+    fprintf(stderr, "usage: %s INPUT-FILE OUTPUT-FILE\n", argv[0]);
+    return 1;
+  }
+
+  inputName = new GooString(argv[1]);
+  outputName = new GooString(argv[2]);
+
+  globalParams = new GlobalParams();
+
+  doc = new PDFDoc(inputName);
+
+  if (!doc->isOk()) {
+    delete doc;
+    fprintf(stderr, "Error loading document !\n");
+    return 1;
+  }
+
+
+  doc->saveAs(outputName, writeForceRewrite);
+
+  delete doc;
+  delete globalParams;
+}
commit 246294714c6011651fd0e5b3649bd65919058c72
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:59:03 2008 +0100

    FormWidget's 'modified' member variable is now updated correctly each time an update is done.
    
    Before, only text FormWidget had their 'modified' field set to 'true' if they had been updated.
    It is now the case for the other type of FormWidgets.

diff --git a/poppler/Form.cc b/poppler/Form.cc
index 887e1dd..8ab8723 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -163,6 +163,7 @@ void FormWidgetButton::setState (GBool astate, GBool calledByParent)
     return;
   //the state modification may be denied by the parent. e.g we don't want to let the user put all combo boxes to false
   if (!calledByParent) { //avoid infinite recursion
+    modified = gTrue;
     if (!parent->setState(childNum, astate)) {
       return;
     }
@@ -348,6 +349,7 @@ void FormWidgetText::setContent(GooString* new_content)
     return;
   }
 
+  modified = gTrue;
   if (new_content == NULL) {
     parent->setContentCopy(NULL);
   } else {
@@ -364,7 +366,6 @@ void FormWidgetText::setContent(GooString* new_content)
     obj.getDict()->set("V", &obj1);
     //notify the xref about the update
     xref->setModifiedObject(&obj, ref);
-    modified = gTrue;
   }
 }
 
@@ -524,6 +525,7 @@ void FormWidgetChoice::select (int i)
     return;
   }
   if (!_checkRange(i)) return;
+  modified = gTrue;
   parent->select(i);
   _updateV();
 }
@@ -535,6 +537,7 @@ void FormWidgetChoice::toggle (int i)
     return;
   }
   if (!_checkRange(i)) return;
+  modified = gTrue;
   parent->toggle(i);
   _updateV();
 }
@@ -545,6 +548,7 @@ void FormWidgetChoice::deselectAll ()
     error(-1, "FormWidgetChoice::deselectAll called on a read only field\n");
     return;
   }
+  modified = gTrue;
   parent->deselectAll();
   _updateV();
 }
@@ -575,6 +579,7 @@ void FormWidgetChoice::setEditChoice (GooString* new_content)
     return;
   }
 
+  modified = gTrue;
   if (new_content == NULL) {
     parent->setEditChoice(NULL);
   } else {
commit 6e0f297b8b17afb95779724b8618ca39016e664a
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:54:09 2008 +0100

    Annot will save their generated appearance in their AP dict.

diff --git a/poppler/Annot.cc b/poppler/Annot.cc
index f03a71d..60a83a8 100644
--- a/poppler/Annot.cc
+++ b/poppler/Annot.cc
@@ -27,6 +27,7 @@
 #include "Form.h"
 #include "Error.h"
 #include "Page.h"
+#include "XRef.h"
 
 #define annotFlagHidden    0x0002
 #define annotFlagPrint     0x0004
@@ -330,6 +331,8 @@ Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog* catalog, Object *
 void Annot::initialize(XRef *xrefA, Dict *acroForm, Dict *dict, Catalog *catalog) {
   Object apObj, asObj, obj1, obj2, obj3;
 
+  appRef.num = 0;
+  appRef.gen = 65535;
   ok = gTrue;
   xref = xrefA;
   appearBuf = NULL;
@@ -548,9 +551,6 @@ Annot::~Annot() {
     delete modified;
 
   appearance.free();
-  if (appearBuf) {
-    delete appearBuf;
-  }
 
   if(appearState)
     delete appearState;
@@ -935,10 +935,44 @@ void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) {
   drObj.free();
 
   // build the appearance stream
-  appearStream = new MemStream(appearBuf->getCString(), 0,
+  appearStream = new MemStream(strdup(appearBuf->getCString()), 0,
       appearBuf->getLength(), &appearDict);
   appearance.free();
   appearance.initStream(appearStream);
+  delete appearBuf;
+
+  appearStream->setNeedFree(gTrue);
+
+  if (widget->isModified()) {
+    //create a new object that will contains the new appearance
+    
+    //if we already have a N entry in our AP dict, reuse it
+    if (annot->lookup("AP", &obj1)->isDict() &&
+        obj1.dictLookupNF("N", &obj2)->isRef()) {
+      appRef = obj2.getRef();
+    }
+
+    // this annot doesn't have an AP yet, create one
+    if (appRef.num == 0)
+      appRef = xref->addIndirectObject(&appearance);
+    else // since we reuse the already existing AP, we have to notify the xref about this update
+      xref->setModifiedObject(&appearance, appRef);
+
+    // update object's AP and AS
+    Object apObj;
+    apObj.initDict(xref);
+
+    Object oaRef;
+    oaRef.initRef(appRef.num, appRef.gen);
+
+    apObj.dictSet("N", &oaRef);
+    annot->set("AP", &apObj);
+    Dict* d = new Dict(annot);
+    Object dictObj;
+    dictObj.initDict(d);
+
+    xref->setModifiedObject(&dictObj, ref);
+  }
 
   if (fontDict) {
     delete fontDict;
diff --git a/poppler/Annot.h b/poppler/Annot.h
index 7456160..c7c90c1 100644
--- a/poppler/Annot.h
+++ b/poppler/Annot.h
@@ -271,6 +271,7 @@ protected:
   GooString *modified;              // M
   Guint flags;                      // F (must be a 32 bit unsigned int)
   //Dict *appearDict;                 // AP (should be correctly parsed)
+  Ref appRef;                       //the reference to the indirect appearance object in XRef 
   Object appearance;     // a reference to the Form XObject stream
                          //   for the normal appearance
   GooString *appearState;           // AS
commit e20f6a8e9ac3936b4bc03710a71fe390dfc4c094
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:52:02 2008 +0100

    Add deep copy constructor to Dict.

diff --git a/poppler/Dict.cc b/poppler/Dict.cc
index 0c74566..be82890 100644
--- a/poppler/Dict.cc
+++ b/poppler/Dict.cc
@@ -30,6 +30,18 @@ Dict::Dict(XRef *xrefA) {
   ref = 1;
 }
 
+Dict::Dict(Dict* dictA) {
+  xref = dictA->xref;
+  size = length = dictA->length;
+  ref = 1;
+
+  entries = (DictEntry *)gmallocn(size, sizeof(DictEntry));
+  for (int i=0; i<length; i++) {
+    entries[i].key = strdup(dictA->entries[i].key);
+    dictA->entries[i].val.copy(&entries[i].val);
+  }
+}
+
 Dict::~Dict() {
   int i;
 
@@ -89,7 +101,7 @@ void Dict::set(char *key, Object *val) {
   e = find (key);
   if (e) {
     e->val.free();
-    e->val = *val;
+    val->copy(&e->val);
   } else {
     add (copyString(key), val);
   }
diff --git a/poppler/Dict.h b/poppler/Dict.h
index 0d1e6a1..badb6b5 100644
--- a/poppler/Dict.h
+++ b/poppler/Dict.h
@@ -29,6 +29,7 @@ public:
 
   // Constructor.
   Dict(XRef *xrefA);
+  Dict(Dict* dictA);
 
   // Destructor.
   ~Dict();
commit e8d46cab77c7167edb0896296118daafc0f13b6d
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:51:44 2008 +0100

    Adds the ability to save PDF using either incremental update or by rewriting completly the PDF.

diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 78fbea2..b3336a0 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -34,6 +34,7 @@
 #include "Lexer.h"
 #include "Parser.h"
 #include "SecurityHandler.h"
+#include "Decrypt.h"
 #ifndef DISABLE_OUTLINE
 #include "Outline.h"
 #endif
@@ -435,19 +436,387 @@ GBool PDFDoc::isLinearized() {
   return lin;
 }
 
-GBool PDFDoc::saveAs(GooString *name) {
+GBool PDFDoc::saveAs(GooString *name, PDFWriteMode mode) {
   FILE *f;
-  int c;
+  OutStream *outStr;
 
   if (!(f = fopen(name->getCString(), "wb"))) {
     error(-1, "Couldn't open file '%s'", name->getCString());
     return gFalse;
   }
+  outStr = new FileOutStream(f,0);
+
+  if (mode == writeForceRewrite) {
+    saveCompleteRewrite(outStr);
+  } else if (mode == writeForceIncremental) {
+    saveIncrementalUpdate(outStr); 
+  } else { // let poppler decide
+    // find if we have updated objects
+    GBool updated = gFalse;
+    for(int i=0; i<xref->getNumObjects(); i++) {
+      if (xref->getEntry(i)->updated) {
+        updated = gTrue;
+        break;
+      }
+    }
+    if(updated) { 
+      saveIncrementalUpdate(outStr);
+    } else {
+      // simply copy the original file
+      int c;
+      str->reset();
+      while ((c = str->getChar()) != EOF) {
+        outStr->put(c);
+      }
+      str->close();
+    }
+  }
+
+  delete outStr;
+  fclose(f);
+  return gTrue;
+}
+
+void PDFDoc::saveIncrementalUpdate (OutStream* outStr)
+{
+  XRef *uxref;
+  int c;
+  //copy the original file
   str->reset();
   while ((c = str->getChar()) != EOF) {
-    fputc(c, f);
+    outStr->put(c);
   }
   str->close();
-  fclose(f);
-  return gTrue;
+
+  uxref = new XRef();
+  uxref->add(0, 65535, 0, gFalse);
+  int objectsCount = 0; //count the number of objects in the XRef(s)
+  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;
+    objectsCount++;
+    if (xref->getEntry(i)->updated) { //we have an updated object
+      Object obj1;
+      Ref ref;
+      ref.num = i;
+      ref.gen = xref->getEntry(i)->gen;
+      xref->fetch(ref.num, ref.gen, &obj1);
+      Guint offset = writeObject(&obj1, &ref, outStr);
+      uxref->add(ref.num, ref.gen, offset, gTrue);
+      obj1.free();
+    }
+  }
+  if (uxref->getSize() == 0) { //we have nothing to update
+    delete uxref;
+    return;
+  }
+
+  Guint uxrefOffset = outStr->getPos();
+  uxref->writeToFile(outStr);
+
+  writeTrailer(uxrefOffset, objectsCount, outStr, gTrue);
+
+  delete uxref;
+}
+
+void PDFDoc::saveCompleteRewrite (OutStream* outStr)
+{
+  outStr->printf("%%PDF-%.1f\r\n",pdfVersion);
+  XRef *uxref = new XRef();
+  uxref->add(0, 65535, 0, gFalse);
+  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 (type == xrefEntryUncompressed){ 
+      ref.num = i;
+      ref.gen = xref->getEntry(i)->gen;
+      xref->fetch(ref.num, ref.gen, &obj1);
+      Guint offset = writeObject(&obj1, &ref, 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);
+      Guint offset = writeObject(&obj1, &ref, outStr);
+      uxref->add(ref.num, ref.gen, offset, gTrue);
+      obj1.free();
+    }
+  }
+  Guint uxrefOffset = outStr->getPos();
+  uxref->writeToFile(outStr);
+
+  writeTrailer(uxrefOffset, uxref->getSize(), outStr, gFalse);
+
+
+  delete uxref;
+
+}
+
+void PDFDoc::writeDictionnary (Dict* dict, OutStream* outStr)
+{
+  Object obj1;
+  outStr->printf("<<");
+  for (int i=0; i<dict->getLength(); i++) {
+    outStr->printf("/%s ", dict->getKey(i));
+    writeObject(dict->getValNF(i, &obj1), NULL, outStr);
+    obj1.free();
+  }
+  outStr->printf(">>");
+}
+
+void PDFDoc::writeStream (Stream* str, OutStream* outStr)
+{
+  int c;
+  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()) {
+    error (-1, "PDFDoc::writeRawStream, no Length in stream dict");
+    return;
+  }
+
+  const int length = obj1.getInt();
+  obj1.free();
+
+  outStr->printf("stream\r\n");
+  str->unfilteredReset();
+  for (int 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)
+{
+  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("(");
+    while(*c!='\0') {
+      char unescaped = (*c)&0x000000ff;
+      //escape if needed
+      if (unescaped == '(' || unescaped == ')' || unescaped == '\\')
+        outStr->printf("%c", '\\');
+      outStr->printf("%c", unescaped);
+      c++;
+    }
+    outStr->printf(") ");
+  }
+}
+
+Guint PDFDoc::writeObject (Object* obj, Ref* ref, OutStream* outStr)
+{
+  Array *array;
+  Object obj1;
+  Guint offset = outStr->getPos();
+  int tmp;
+
+  if(ref) 
+    outStr->printf("%i %i obj", ref->num, ref->gen);
+
+  switch (obj->getType()) {
+    case objBool:
+      outStr->printf("%s ", obj->getBool()?"true":"false");
+      break;
+    case objInt:
+      outStr->printf("%i ", obj->getInt());
+      break;
+    case objReal:
+      outStr->printf("%g ", obj->getReal());
+      break;
+    case objString:
+      writeString(obj->getString(), outStr);
+      break;
+    case objName:
+      outStr->printf("/%s ", obj->getName());
+      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), NULL,outStr);
+        obj1.free();
+      }
+      outStr->printf("]");
+      break;
+    case objDict:
+      writeDictionnary (obj->getDict(),outStr);
+      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) {
+          //we write the stream unencoded => TODO: write stream encoder
+          stream->reset();
+          //recalculate stream length
+          tmp = 0;
+          for (int c=stream->getChar(); c!=EOF; c=stream->getChar()) {
+            tmp++;
+          }
+          obj1.initInt(tmp);
+          stream->getDict()->set("Length", &obj1);
+
+          //Remove Stream encoding
+          stream->getDict()->remove("Filter");
+          stream->getDict()->remove("DecodeParms");
+
+          writeDictionnary (stream->getDict(),outStr);
+          writeStream (stream,outStr);
+          obj1.free();
+        } else {
+          //raw stream copy
+          writeDictionnary (stream->getDict(), outStr);
+          writeRawStream (stream, outStr);
+        }
+        break;
+      }
+    case objRef:
+      outStr->printf("%i %i R ", obj->getRef().num, obj->getRef().gen);
+      break;
+    case objCmd:
+      outStr->printf("cmd\r\n");
+      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(-1,"Unhandled objType : %i, please report a bug with a testcase\r\n", obj->getType());
+      break;
+  }
+  if (ref)
+    outStr->printf("endobj\r\n");
+  return offset;
+}
+
+void PDFDoc::writeTrailer (Guint uxrefOffset, int uxrefSize, OutStream* outStr, GBool incrUpdate)
+{
+  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);
+  message.append(fileName);
+  // file size
+  unsigned int fileSize = 0;
+  int c;
+  str->reset();
+  while ((c = str->getChar()) != EOF) {
+    fileSize++;
+  }
+  str->close();
+  sprintf(buffer, "%i", fileSize);
+  message.append(buffer);
+
+  //info dict -- only use text string
+  if (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();
+
+  //calculate md5 digest
+  Guchar digest[16];
+  Decrypt::md5((Guchar*)message.getCString(), message.getLength(), digest);
+  obj1.initString(new GooString((const char*)digest, 16));
+
+  //create ID array
+  Object obj2,obj3,obj4;
+  obj2.initArray(xref);
+
+  if (incrUpdate) {
+    //only update the second part of the array
+    if(xref->getTrailerDict()->getDict()->lookup("ID", &obj4) != NULL) {
+      if (!obj4.isArray()) {
+        error(-1, "PDFDoc::writeTrailer 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);
+      }
+    }
+  } else {
+    //new file => same values for the two identifiers
+    obj2.arrayAdd(&obj1);
+    obj2.arrayAdd(&obj1);
+    trailerDict->set("ID", &obj2);
+  }
+
+
+  obj1.initRef(xref->getRootNum(), xref->getRootGen());
+  trailerDict->set("Root", &obj1);
+  obj1.free();
+
+  if (incrUpdate) { 
+    obj1.initInt(xref->getLastXRefPos());
+    trailerDict->set("Prev", &obj1);
+    obj1.free();
+  }
+  outStr->printf( "trailer\r\n");
+  writeDictionnary(trailerDict, outStr);
+  outStr->printf( "\r\nstartxref\r\n");
+  outStr->printf( "%i\r\n", uxrefOffset);
+  outStr->printf( "%%%%EOF\r\n");
 }
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index e6992ba..2336694 100644
--- a/poppler/PDFDoc.h
+++ b/poppler/PDFDoc.h
@@ -27,6 +27,12 @@ class LinkAction;
 class LinkDest;
 class Outline;
 
+enum PDFWriteMode {
+  writeStandard,
+  writeForceRewrite,
+  writeForceIncremental
+};
+
 //------------------------------------------------------------------------
 // PDFDoc
 //------------------------------------------------------------------------
@@ -169,12 +175,22 @@ public:
   double getPDFVersion() { return pdfVersion; }
 
   // Save this file with another name.
-  GBool saveAs(GooString *name);
+  GBool saveAs(GooString *name, PDFWriteMode mode=writeStandard);
 
   // Return a pointer to the GUI (XPDFCore or WinPDFCore object).
   void *getGUIData() { return guiData; }
 
 private:
+  // Add object to current file stream and return the offset of the beginning of the object
+  Guint writeObject (Object *obj, Ref *ref, OutStream* outStr);
+  void writeDictionnary (Dict* dict, OutStream* outStr);
+  void writeStream (Stream* str, OutStream* outStr);
+  void writeRawStream (Stream* str, OutStream* outStr);
+  void writeTrailer (Guint uxrefOffset, int uxrefSize, OutStream* outStr, GBool incrUpdate);
+  void writeString (GooString* s, OutStream* outStr);
+  void saveIncrementalUpdate (OutStream* outStr);
+  void saveCompleteRewrite (OutStream* outStr);
+
 
   GBool setup(GooString *ownerPassword, GooString *userPassword);
   GBool checkFooter();
diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 9716477..dd3593a 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -524,6 +524,7 @@ GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) {
       entries[i].offset = 0xffffffff;
       entries[i].type = xrefEntryFree;
       entries[i].obj.initNull ();
+      entries[i].updated = false;
     }
     size = newSize;
   }
@@ -619,6 +620,7 @@ GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) {
       entries[i].offset = 0xffffffff;
       entries[i].type = xrefEntryFree;
       entries[i].obj.initNull ();
+      entries[i].updated = false;
     }
     size = newSize;
   }
@@ -764,6 +766,7 @@ GBool XRef::constructXRef() {
 		    entries[i].offset = 0xffffffff;
 		    entries[i].type = xrefEntryFree;
 		    entries[i].obj.initNull ();
+        entries[i].updated = false;
 		  }
 		  size = newSize;
 		}
@@ -1006,6 +1009,7 @@ void XRef::add(int num, int gen, Guint offs, GBool used) {
   e->gen = gen;
   e->num = num;
   e->obj.initNull ();
+  e->updated = false;
   if (used) {
     e->type = xrefEntryUncompressed;
     e->offset = offs;
@@ -1021,6 +1025,7 @@ void XRef::setModifiedObject (Object* o, Ref r) {
     return;
   }
   o->copy(&entries[r.num].obj);
+  entries[r.num].updated = true;
 }
 
 Ref XRef::addIndirectObject (Object* o) {
@@ -1054,6 +1059,7 @@ Ref XRef::addIndirectObject (Object* o) {
 
   entries[newEntry].type = xrefEntryUncompressed;
   o->copy(&entries[newEntry].obj);
+  entries[newEntry].updated = true;
 
   Ref r;
   r.num = entries[newEntry].num;
@@ -1083,16 +1089,16 @@ void XRef::writeToFile(OutStream* outStr) {
   }
   //write the new xref
   int j;
-  outStr->printf(file,"xref\r\n");
+  outStr->printf("xref\r\n");
   for (int i=0; i<size; i++) {
     for(j=i; j<size; j++) { //look for consecutive entries
       if (j!=i && entries[j].num != entries[j-1].num+1) 
               break;
     }
-    outStr->printf(file,"%i %i\r\n", entries[i].num, j-i);
+    outStr->printf("%i %i\r\n", entries[i].num, j-i);
     for (int k=i; k<j; k++) {
       if(entries[k].gen > 65535) entries[k].gen = 65535; //cap generation number to 65535 (required by PDFReference)
-      outStr->printf(file,"%010i %05i %c\r\n", entries[k].offset, entries[k].gen, (entries[k].type==xrefEntryFree)?'f':'n');
+      outStr->printf("%010i %05i %c\r\n", entries[k].offset, entries[k].gen, (entries[k].type==xrefEntryFree)?'f':'n');
     }
     i = j-1;
   }
diff --git a/poppler/XRef.h b/poppler/XRef.h
index d5a395b..4a25515 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -36,7 +36,8 @@ struct XRefEntry {
   int gen;
   int num;
   XRefEntryType type;
-  Object obj;
+  bool updated;
+  Object obj; //if this entry was updated, obj will contains the updated object
 };
 
 class XRef {
commit 742b0c3dec01d8672b84f56d5bb0e2890b178594
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:51:27 2008 +0100

    Make the md5 method of Decrypt public so it can be used by other files.

diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc
index 3cf24ef..cc148ba 100644
--- a/poppler/Decrypt.cc
+++ b/poppler/Decrypt.cc
@@ -21,7 +21,6 @@ static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c);
 static void aesKeyExpansion(DecryptAESState *s,
 			    Guchar *objKey, int objKeyLen);
 static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last);
-static void md5(Guchar *msg, int msgLen, Guchar *digest);
 
 static Guchar passwordPad[32] = {
   0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
@@ -208,7 +207,7 @@ DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey,
   } else {
     n = keyLength + 5;
   }
-  md5(objKey, n, objKey);
+  Decrypt::md5(objKey, n, objKey);
   if ((objKeyLength = keyLength + 5) > 16) {
     objKeyLength = 16;
   }
@@ -633,7 +632,7 @@ static inline Gulong md5Round4(Gulong a, Gulong b, Gulong c, Gulong d,
   return b + rotateLeft((a + (c ^ (b | ~d)) + Xk + Ti), s);
 }
 
-static void md5(Guchar *msg, int msgLen, Guchar *digest) {
+void Decrypt::md5(Guchar *msg, int msgLen, Guchar *digest) {
   Gulong x[16];
   Gulong a, b, c, d, aa, bb, cc, dd;
   int n64;
diff --git a/poppler/Decrypt.h b/poppler/Decrypt.h
index b2f754f..b861968 100644
--- a/poppler/Decrypt.h
+++ b/poppler/Decrypt.h
@@ -24,6 +24,7 @@
 
 class Decrypt {
 public:
+  static void md5(Guchar *msg, int msgLen, Guchar *digest);
 
   // Generate a file key.  The <fileKey> buffer must have space for at
   // least 16 bytes.  Checks <ownerPassword> and then <userPassword>
commit bb7867976740dea259d4110c072552fc5953910f
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:51:07 2008 +0100

    Modify the writeToFile method of XRef so it uses OutStream instead of a C file descriptor.

diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index 4f239dd..9716477 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -1068,7 +1068,7 @@ int compare (const void* a, const void* b)
   return (((XRefEntry*)a)->num - ((XRefEntry*)b)->num);
 }
 
-void XRef::writeToFile(FILE* file) {
+void XRef::writeToFile(OutStream* outStr) {
   qsort(entries, size, sizeof(XRefEntry), compare);
   //create free entries linked-list
   if (entries[0].gen != 65535) {
@@ -1083,16 +1083,16 @@ void XRef::writeToFile(FILE* file) {
   }
   //write the new xref
   int j;
-  fprintf(file,"xref\r\n");
+  outStr->printf(file,"xref\r\n");
   for (int i=0; i<size; i++) {
-    for(j=i; j<size; j++) { //look for consecutive entry
+    for(j=i; j<size; j++) { //look for consecutive entries
       if (j!=i && entries[j].num != entries[j-1].num+1) 
               break;
     }
-    fprintf(file,"%i %i\r\n", entries[i].num, j-i);
+    outStr->printf(file,"%i %i\r\n", entries[i].num, j-i);
     for (int k=i; k<j; k++) {
       if(entries[k].gen > 65535) entries[k].gen = 65535; //cap generation number to 65535 (required by PDFReference)
-      fprintf(file,"%010i %05i %c\r\n", entries[k].offset, entries[k].gen, (entries[k].type==xrefEntryFree)?'f':'n');
+      outStr->printf(file,"%010i %05i %c\r\n", entries[k].offset, entries[k].gen, (entries[k].type==xrefEntryFree)?'f':'n');
     }
     i = j-1;
   }
diff --git a/poppler/XRef.h b/poppler/XRef.h
index cc4d229..d5a395b 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -111,7 +111,7 @@ public:
   void setModifiedObject(Object* o, Ref r);
   Ref addIndirectObject (Object* o);
   void add(int num, int gen,  Guint offs, GBool used);
-  void writeToFile(FILE* f);
+  void writeToFile(OutStream* outStr);
 
 private:
 
commit d80736587fdbc0e163077f27bfd21c5e3a7fa4c7
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:50:49 2008 +0100

    Adds addIndirectObject method to XRef. This method allow the creation of new indirect objects.

diff --git a/poppler/XRef.cc b/poppler/XRef.cc
index b84e198..4f239dd 100644
--- a/poppler/XRef.cc
+++ b/poppler/XRef.cc
@@ -1023,6 +1023,45 @@ void XRef::setModifiedObject (Object* o, Ref r) {
   o->copy(&entries[r.num].obj);
 }
 
+Ref XRef::addIndirectObject (Object* o) {
+  //Find the next free entry
+  // lastEntry is the last free entry we encountered,
+  // entry 0 is always free
+  int lastEntry = 0;
+  int newEntry = entries[0].offset;
+
+  do {
+    lastEntry = newEntry;
+    newEntry = entries[newEntry].offset;
+    //we are looking for a free entry that we can reuse
+    //(=> gen number != 65535)
+  } while ((newEntry != 0) && (entries[newEntry].gen == 65535));
+
+  //the linked list of free entry is empty => create a new one
+  if (newEntry == 0) {
+    newEntry = size;
+    size++;
+    entries = (XRefEntry *)greallocn(entries, size, sizeof(XRefEntry));
+    entries[newEntry].gen = 0;
+    entries[newEntry].num = newEntry;
+  } else { //reuse a free entry
+    //'remove' the entry we are using from the free entry linked list
+    entries[lastEntry].offset = entries[newEntry].offset;
+    entries[newEntry].num = newEntry;
+    //we don't touch gen number, because it should have been 
+    //incremented when the object was deleted
+  }
+
+  entries[newEntry].type = xrefEntryUncompressed;
+  o->copy(&entries[newEntry].obj);
+
+  Ref r;
+  r.num = entries[newEntry].num;
+  r.gen = entries[newEntry].gen;
+  return r;
+}
+
+
 //used to sort the entries
 int compare (const void* a, const void* b)
 {
diff --git a/poppler/XRef.h b/poppler/XRef.h
index 05699c4..cc4d229 100644
--- a/poppler/XRef.h
+++ b/poppler/XRef.h
@@ -109,6 +109,7 @@ public:
 
   // Write access
   void setModifiedObject(Object* o, Ref r);
+  Ref addIndirectObject (Object* o);
   void add(int num, int gen,  Guint offs, GBool used);
   void writeToFile(FILE* f);
 
commit 8bd00dd0872191b8806e9411d9a1adc441f08a47
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:50:16 2008 +0100

    Add some unfiltered methods to input stream.
    
    With these methods, it is possible to read the raw content from the stream, without any filtering (even not the headers).

diff --git a/poppler/Stream.cc b/poppler/Stream.cc
index 2422f8a..3ef7146 100644
--- a/poppler/Stream.cc
+++ b/poppler/Stream.cc
@@ -1340,10 +1340,21 @@ CCITTFaxStream::~CCITTFaxStream() {
   gfree(codingLine);
 }
 
+void CCITTFaxStream::unfilteredReset () {
+  str->reset();
+
+  row = 0;
+  nextLine2D = encoding < 0;
+  inputBits = 0;
+  a0i = 0;
+  outputBits = 0;
+  buf = EOF;
+}
+
 void CCITTFaxStream::reset() {
   short code1;
 
-  str->reset();
+  unfilteredReset();
 
   if (codingLine != NULL && refLine != NULL) {
     eof = gFalse;
@@ -1351,12 +1362,6 @@ void CCITTFaxStream::reset() {
   } else {
     eof = gTrue;
   }
-  row = 0;
-  nextLine2D = encoding < 0;
-  inputBits = 0;
-  a0i = 0;
-  outputBits = 0;
-  buf = EOF;
 
   // skip any initial zero bits and end-of-line marker, and get the 2D
   // encoding tag
@@ -2027,9 +2032,7 @@ DCTStream::~DCTStream() {
   delete str;
 }
 
-void DCTStream::reset() {
-  int i, j;
-
+void DCTStream::unfilteredReset() {
   str->reset();
 
   progressive = interleaved = gFalse;
@@ -2038,9 +2041,17 @@ void DCTStream::reset() {
   numQuantTables = 0;
   numDCHuffTables = 0;
   numACHuffTables = 0;
+  colorXform = 0;
   gotJFIFMarker = gFalse;
   gotAdobeMarker = gFalse;
   restartInterval = 0;
+}
+
+
+void DCTStream::reset() {
+  int i, j;
+
+  unfilteredReset();
 
   if (!readHeader()) {
     y = height;
@@ -4041,9 +4052,7 @@ FlateStream::~FlateStream() {
   delete str;
 }
 
-void FlateStream::reset() {
-  int cmf, flg;
-
+void FlateStream::unfilteredReset() {
   index = 0;
   remain = 0;
   codeBuf = 0;
@@ -4053,6 +4062,12 @@ void FlateStream::reset() {
   eof = gTrue;
 
   str->reset();
+}
+
+void FlateStream::reset() {
+  int cmf, flg;
+
+  unfilteredReset();
 
   // read header
   //~ need to look at window size?
diff --git a/poppler/Stream.h b/poppler/Stream.h
index 68418af..74152fc 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -87,6 +87,14 @@ public:
   // This is only used by StreamPredictor.
   virtual int getRawChar();
 
+  // Get next char directly from stream source, without filtering it
+  virtual int getUnfilteredChar () = 0;
+
+  // Resets the stream without reading anything (even not the headers)
+  // WARNING: Reading the stream with something else than getUnfilteredChar 
+  // may lead to unexcepted behaviour until you call reset ()
+  virtual void unfilteredReset () = 0;
+
   // Get next line from stream.
   virtual char *getLine(char *buf, int size);
 
@@ -247,6 +255,9 @@ public:
   virtual Dict *getDict() { return str->getDict(); }
   virtual Stream *getNextStream() { return str; }
 
+  virtual int getUnfilteredChar () { return str->getUnfilteredChar(); }
+  virtual void unfilteredReset () { str->unfilteredReset(); }
+
 protected:
 
   Stream *str;
@@ -353,6 +364,9 @@ public:
   virtual Guint getStart() { return start; }
   virtual void moveStart(int delta);
 
+  virtual int getUnfilteredChar () { return getChar(); }
+  virtual void unfilteredReset () { reset(); }
+
 private:
 
   GBool fillBuf();
@@ -396,6 +410,9 @@ public:
   //otherwise it will not touch it. Default value is false
   virtual void setNeedFree (GBool val) { needFree = val; }
 
+  virtual int getUnfilteredChar () { return getChar(); }
+  virtual void unfilteredReset () { reset (); } 
+
 private:
 
   char *buf;
@@ -432,6 +449,10 @@ public:
   virtual Guint getStart();
   virtual void moveStart(int delta);
 
+  virtual int getUnfilteredChar () { return str->getUnfilteredChar(); }
+  virtual void unfilteredReset () { str->unfilteredReset(); }
+
+
 private:
 
   Stream *str;
@@ -580,6 +601,8 @@ public:
   virtual GooString *getPSFilter(int psLevel, char *indent);
   virtual GBool isBinary(GBool last = gTrue);
 
+  virtual void unfilteredReset ();
+
 private:
 
   int encoding;			// 'K' parameter
@@ -655,6 +678,8 @@ public:
   virtual GBool isBinary(GBool last = gTrue);
   Stream *getRawStream() { return str; }
 
+  virtual void unfilteredReset();
+
 private:
 
   GBool progressive;		// set if in progressive mode
@@ -759,6 +784,7 @@ public:
   virtual int getRawChar();
   virtual GooString *getPSFilter(int psLevel, char *indent);
   virtual GBool isBinary(GBool last = gTrue);
+  virtual void unfilteredReset ();
 
 private:
 
commit 8bcda287ddd316f90b3e47a3a307fbe63a5c21f7
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:46:53 2008 +0100

    Add setNeedFree method to MemStream so it is possible to choose if the stream should take care of deleting the buffer.

diff --git a/poppler/Stream.h b/poppler/Stream.h
index 8b6f4e3..68418af 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -392,6 +392,10 @@ public:
   virtual Guint getStart() { return start; }
   virtual void moveStart(int delta);
 
+  //if needFree = true, the stream will delete buf when it is destroyed
+  //otherwise it will not touch it. Default value is false
+  virtual void setNeedFree (GBool val) { needFree = val; }
+
 private:
 
   char *buf;
commit 4fbd143de7e3a8ab386dd14b057e62b3b9fe04e4
Author: Julien Rebetez <julien at fhtagn.net>
Date:   Sat Jan 19 12:45:54 2008 +0100

    Add Outstream, a base class for output streams and FileOutStream, which implements OutStream for output to a file.

diff --git a/poppler/Stream.cc b/poppler/Stream.cc
index 3e44e27..2422f8a 100644
--- a/poppler/Stream.cc
+++ b/poppler/Stream.cc
@@ -283,6 +283,61 @@ Stream *Stream::makeFilter(char *name, Stream *str, Object *params) {
 }
 
 //------------------------------------------------------------------------
+// OutStream
+//------------------------------------------------------------------------
+OutStream::OutStream ()
+{
+  ref = 1;
+}
+
+OutStream::~OutStream ()
+{
+}
+
+//------------------------------------------------------------------------
+// FileOutStream
+//------------------------------------------------------------------------
+FileOutStream::FileOutStream (FILE* fa, Guint startA)
+{
+  f = fa;
+  start = startA;
+}
+
+FileOutStream::~FileOutStream ()
+{
+  close ();
+}
+
+void FileOutStream::reset ()
+{
+  fseek(f, start, SEEK_SET);
+}
+
+void FileOutStream::close ()
+{
+
+}
+
+int FileOutStream::getPos ()
+{
+  return ftell(f);
+}
+
+void FileOutStream::put (char c)
+{
+  fputc(c,f);
+}
+
+void FileOutStream::printf(const char *format, ...)
+{
+  va_list argptr;
+  va_start (argptr, format);
+  vfprintf(f, format, argptr);
+  va_end (argptr);
+}
+
+
+//------------------------------------------------------------------------
 // BaseStream
 //------------------------------------------------------------------------
 
diff --git a/poppler/Stream.h b/poppler/Stream.h
index 5b176f8..8b6f4e3 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -135,6 +135,70 @@ private:
   int ref;			// reference count
 };
 
+
+ //------------------------------------------------------------------------
+// OutStream
+//
+// This is the base class for all streams that output to a file
+//------------------------------------------------------------------------
+class OutStream {
+public:
+  // Constructor.
+  OutStream ();
+
+  // Desctructor.
+  virtual ~OutStream ();
+
+  // Reference counting.
+  int incRef() { return ++ref; }
+  int decRef() { return --ref; }
+
+  // Reset stream to beginning
+  virtual void reset() = 0;
+
+  // Close the stream
+  virtual void close() = 0;
+
+  // Return position in stream
+  virtual int getPos() = 0;
+
+  // Put a char in the stream
+  virtual void put (char c) = 0;
+
+  //FIXME
+  // Printf-like function                         2,3 because the first arg is class instance ?
+  virtual void printf (const char *format, ...) = 0 ; //__attribute__((format(printf, 2,3))) = 0;
+
+private:
+  int ref; // reference count
+    
+};
+
+//------------------------------------------------------------------------
+// FileOutStream
+//------------------------------------------------------------------------
+class FileOutStream : public OutStream {
+public:
+  FileOutStream (FILE* fa, Guint startA);
+
+  virtual ~FileOutStream ();
+
+  virtual void reset();
+
+  virtual void close();
+
+  virtual int getPos();
+
+  virtual void put (char c);
+
+  virtual void printf (const char *format, ...);
+private:
+  FILE *f;
+  Guint start;
+
+};
+
+
 //------------------------------------------------------------------------
 // BaseStream
 //


More information about the poppler mailing list