Index: poppler/PDFDoc.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/PDFDoc.cc,v retrieving revision 1.10 diff -u -8 -p -r1.10 PDFDoc.cc --- poppler/PDFDoc.cc 18 Jan 2006 22:32:13 -0000 1.10 +++ poppler/PDFDoc.cc 26 Feb 2007 22:42:26 -0000 @@ -120,16 +120,17 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int f file = NULL; str = NULL; xref = NULL; catalog = NULL; links = NULL; #ifndef DISABLE_OUTLINE outline = NULL; #endif + form = NULL; //~ file name should be stored in Unicode (?) fileName = new GooString(); for (i = 0; i < fileNameLen; ++i) { fileName->append((char)fileNameA[i]); } // zero-terminate the file name string @@ -196,25 +197,26 @@ GBool PDFDoc::setup(GooString *ownerPass } // check for encryption if (!checkEncryption(ownerPassword, userPassword)) { errCode = errEncrypted; return gFalse; } + // read catalog catalog = new Catalog(xref); if (!catalog->isOk()) { error(-1, "Couldn't read page catalog"); errCode = errBadCatalog; return gFalse; } -#ifndef DISABLE_OUTLINE + #ifndef DISABLE_OUTLINE // read outline outline = new Outline(catalog->getOutline(), xref); #endif // done return gTrue; } @@ -456,31 +458,333 @@ GBool PDFDoc::isLinearized() { obj4.free(); obj3.free(); obj2.free(); obj1.free(); delete parser; return lin; } -GBool PDFDoc::saveAs(GooString *name) { +GBool PDFDoc::saveAs(GooString *name, PDFWriteMode mode) { FILE *f; - int c; if (!(f = fopen(name->getCString(), "wb"))) { error(-1, "Couldn't open file '%s'", name->getCString()); return gFalse; } + + if (mode == writeForceRewrite) { + saveCompleteRewrite(f); + } else if (mode == writeForceIncremental) { + saveIncrementalUpdate(f); + } else { // let poppler decide + // find if we have updated objects + GBool updated = gFalse; + for(int i=0; igetNumObjects(); i++) { + if (xref->getEntry(i)->obj) { + updated = gTrue; + break; + } + } + if(updated) { + saveCompleteRewrite(f); + } else { + // simply copy the original file + int c; + str->reset(); + while ((c = str->getChar()) != EOF) { + fputc(c, f); + } + str->close(); + } + } + + fclose(f); + return gTrue; +} + +void PDFDoc::saveIncrementalUpdate (FILE *f) +{ + XRef *uxref; + int c; + //copy the original file str->reset(); while ((c = str->getChar()) != EOF) { fputc(c, f); } 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; igetNumObjects(); i++) { + if ((xref->getEntry(i)->type == xrefEntryFree) && + (xref->getEntry(i)->gen == 0)) //we skip the irrelevant free objects + continue; + objectsCount++; + if (xref->getEntry(i)->obj) { //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, f); + uxref->add(ref.num, ref.gen, offset, gTrue); + obj1.free(); + } + } + if (uxref->getSize() == 0) { //we have nothing to update + delete uxref; + return; + } + + Guint uxrefOffset = ftell(f); + uxref->writeToFile(f); + + writeTrailer(uxrefOffset, objectsCount, f, gTrue); + + delete uxref; +} + +void PDFDoc::saveCompleteRewrite (FILE *f) +{ + fprintf(f, "%%PDF-%.1f\r\n",pdfVersion); + XRef *uxref = new XRef(); + uxref->add(0, 65535, 0, gFalse); + for(int i=0; igetNumObjects(); 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 add 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, f); + 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, f); + uxref->add(ref.num, ref.gen, offset, gTrue); + obj1.free(); + } + } + Guint uxrefOffset = ftell(f); + uxref->writeToFile(f); + + writeTrailer(uxrefOffset, uxref->getSize(), f, gFalse); + + + delete uxref; + +} + +void PDFDoc::writeDictionnary (Dict* dict, FILE *f) +{ + Object obj1; + fprintf(f,"<< "); + for (int i=0; igetLength(); i++) { + fprintf(f,"/%s ", dict->getKey(i)->getCString()); + writeObject(dict->getValNF(i, &obj1), NULL, f); + fprintf(f,"\r\n"); + + obj1.free(); + } + fprintf(f,">>\r\n"); +} + +void PDFDoc::writeStream (Stream* str, FILE *f) +{ + int c; + fprintf(f,"stream\r\n"); + str->reset(); + for (int c=str->getChar(); c!= EOF; c=str->getChar()) { + fprintf(f,"%c", c); + } + fprintf(f,"\r\nendstream\r\n"); +} + +void PDFDoc::writeRawStream (Stream* str, FILE *f) +{ + Object obj1; + str->getDict()->lookup("Length", &obj1); + if (!obj1.isInt()) { + error (-1, "PDFDoc::writeRawStream, no Length in stream dict"); + return; + } + +// int c; + const int length = obj1.getInt(); + obj1.free(); + + fprintf(f,"stream\r\n"); + str->unfilteredReset(); + //for (int c=str->getUnfilteredChar(); c!= EOF; c=str->getUnfilteredChar()) { + for (int i=0; igetUnfilteredChar(); + fprintf(f,"%c", c); + } + fprintf(f,"\r\nendstream\r\n"); +} + +void PDFDoc::writeString (GooString* s, FILE* f) +{ + if (s->hasUnicodeMarker()) { + //unicode string don't necessary end with \0 + const char* c = s->getCString(); + fprintf(f, "("); + for(int i=0; igetLength(); i++) { + fprintf(f, /*"%02x"*/"%c", *(c+i)&0x000000ff); + } + fprintf(f, ") "); + } else { + const char* c = s->getCString(); + fprintf(f, "("); + while(*c!='\0') { + fprintf(f, /*"%02x"*/"%c", (*c)&0x000000ff); + c++; + } + fprintf(f, ") "); + } +} + +Guint PDFDoc::writeObject (Object* obj, Ref* ref, FILE *f) +{ + Array *array; + Object obj1; + Guint offset = ftell(f); + int tmp; + + if(ref) + fprintf(f,"%i %i obj\r\n", ref->num, ref->gen); + + switch (obj->getType()) { + case objBool: + fprintf(f,"%s ", obj->getBool()?"true":"false"); + break; + case objInt: + fprintf(f,"%i ", obj->getInt()); + break; + case objReal: + fprintf(f,"%f ", obj->getReal()); + break; + case objString: + writeString(obj->getString(), f); + break; + case objName: + fprintf(f,"/%s ", obj->getName()); + break; + case objNull: + fprintf(f, "null\r\n"); + break; + case objArray: + array = obj->getArray(); + fprintf(f,"["); + for (int i=0; igetLength(); i++) { + writeObject(array->getNF(i, &obj1), NULL,f); + obj1.free(); + } + fprintf(f,"]"); + break; + case objDict: + writeDictionnary (obj->getDict(),f); + break; + case objStream: + { + //We can't modify stream with the current implementation (no write functions in Stream API) + // => the only type of streams for which we can't copy the raw data 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(),f); + writeStream (stream,f); + obj1.free(); + } else { + //raw stream copy + writeDictionnary (stream->getDict(), f); + writeRawStream (stream, f); + } + break; + } + case objRef: + fprintf(f,"%i %i R ", obj->getRef().num, obj->getRef().gen); + break; + case objCmd: + printf("objCmd\n"); + fprintf(f,"null\r\n"); + break; + case objError: + printf("objError\n"); + fprintf(f,"null\r\n"); + break; + case objEOF: + printf("objEOF\n"); + fprintf(f,"null\r\n"); + break; + case objNone: + printf("objNone\n"); + fprintf(f,"null\r\n"); + break; + default: + error(-1,"Unhandled objType : %i, please report a bug with a testcase\r\n", obj->getType()); + break; + } + if (ref) + fprintf(f,"endobj\r\n\r\n"); + return offset; +} + +void PDFDoc::writeTrailer (Guint uxrefOffset, int uxrefSize, FILE* f, GBool incrUpdate) +{ + //Dict* trailerDict = xref->getTrailerDict()->getDict(); + Dict *trailerDict = new Dict(xref); + Object obj1; + obj1.initInt(uxrefSize); + trailerDict->set("Size", &obj1); + obj1.free(); + if(xref->getTrailerDict()->getDict()->lookup("ID", &obj1) != NULL) { + trailerDict->set("ID", &obj1); + obj1.free(); + } + obj1.initRef(xref->getRootNum(), xref->getRootGen()); + trailerDict->set("Root", &obj1); + obj1.free(); + + if (incrUpdate) { + obj1.initInt(xref->getLastXRefPos()); + trailerDict->set("Prev", &obj1); + obj1.free(); + } + fprintf(f, "trailer\r\n"); + writeDictionnary(trailerDict, f); + fprintf(f, "startxref\r\n"); + fprintf(f, "%i\r\n", uxrefOffset); + fprintf(f, "%%%%EOF\r\n"); } void PDFDoc::getLinks(Page *page) { Object obj; links = new Links(page->getAnnots(&obj), catalog->getBaseURI()); obj.free(); } Index: poppler/PDFDoc.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/PDFDoc.h,v retrieving revision 1.7 diff -u -8 -p -r1.7 PDFDoc.h --- poppler/PDFDoc.h 18 Jan 2006 22:32:13 -0000 1.7 +++ poppler/PDFDoc.h 26 Feb 2007 22:42:26 -0000 @@ -22,16 +22,22 @@ class GooString; class BaseStream; class OutputDev; class Links; class LinkAction; class LinkDest; class Outline; +enum PDFWriteMode { + writeStandard, + writeForceRewrite, + writeForceIncremental +}; + //------------------------------------------------------------------------ // PDFDoc //------------------------------------------------------------------------ class PDFDoc { public: PDFDoc(GooString *fileNameA, GooString *ownerPassword = NULL, @@ -158,23 +164,32 @@ public: // Return the document's Info dictionary (if any). Object *getDocInfo(Object *obj) { return xref->getDocInfo(obj); } Object *getDocInfoNF(Object *obj) { return xref->getDocInfoNF(obj); } // Return the PDF version specified by the file. double getPDFVersion() { return pdfVersion; } - // Save this file with another name. - GBool saveAs(GooString *name); + // Save this file with another 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, FILE* f); + void writeDictionnary (Dict* dict, FILE* f); + void writeStream (Stream* str, FILE* f); + void writeRawStream (Stream* str, FILE* f); + void writeTrailer (Guint uxrefOffset, int uxrefSize, FILE* f, GBool incrUpdate); + void writeString (GooString* s, FILE* f); + void saveIncrementalUpdate (FILE* f); + void saveCompleteRewrite (FILE* f); GBool setup(GooString *ownerPassword, GooString *userPassword); GBool checkFooter(); void checkHeader(); GBool checkEncryption(GooString *ownerPassword, GooString *userPassword); void getLinks(Page *page); GooString *fileName; Index: poppler/Stream.cc =================================================================== RCS file: /cvs/poppler/poppler/poppler/Stream.cc,v retrieving revision 1.15 diff -u -8 -p -r1.15 Stream.cc --- poppler/Stream.cc 13 Jan 2007 23:19:21 -0000 1.15 +++ poppler/Stream.cc 26 Feb 2007 22:42:32 -0000 @@ -627,16 +627,17 @@ void FileStream::reset() { #endif saved = gTrue; bufPtr = bufEnd = buf; bufPos = start; if (decrypt) decrypt->reset(); } + void FileStream::close() { if (saved) { #if HAVE_FSEEKO fseeko(f, savePos, SEEK_SET); #elif HAVE_FSEEK64 fseek64(f, savePos, SEEK_SET); #else fseek(f, savePos, SEEK_SET); @@ -1312,29 +1313,31 @@ CCITTFaxStream::CCITTFaxStream(Stream *s } CCITTFaxStream::~CCITTFaxStream() { delete str; gfree(refLine); gfree(codingLine); } -void CCITTFaxStream::reset() { - short code1; - +void CCITTFaxStream::unfilteredReset() { str->reset(); eof = gFalse; row = 0; nextLine2D = encoding < 0; inputBits = 0; codingLine[0] = 0; codingLine[1] = refLine[2] = columns; a0 = 1; buf = EOF; +} +void CCITTFaxStream::reset() { + short code1; + unfilteredReset(); // skip any initial zero bits and end-of-line marker, and get the 2D // encoding tag while ((code1 = lookBits(12)) == 0) { eatBits(1); } if (code1 == 0x001) { eatBits(12); } @@ -1912,31 +1915,35 @@ DCTStream::~DCTStream() { for (i = 0; i < numComps; ++i) { for (j = 0; j < mcuHeight; ++j) { gfree(rowBuf[i][j]); } } } } -void DCTStream::reset() { - int i, j; - +void DCTStream::unfilteredReset() { str->reset(); progressive = interleaved = gFalse; width = height = 0; numComps = 0; 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; return; } // compute MCU size if (numComps == 1) { @@ -3897,28 +3904,32 @@ FlateStream::~FlateStream() { gfree(distCodeTab.codes); } if (pred) { delete pred; } delete str; } -void FlateStream::reset() { - int cmf, flg; - +void FlateStream::unfilteredReset() { index = 0; remain = 0; codeBuf = 0; codeSize = 0; compressedBlock = gFalse; endOfBlock = gTrue; eof = gTrue; str->reset(); +} + +void FlateStream::reset() { + int cmf, flg; + + unfilteredReset(); // read header //~ need to look at window size? endOfBlock = eof = gTrue; cmf = str->getChar(); flg = str->getChar(); if (cmf == EOF || flg == EOF) return; Index: poppler/Stream.h =================================================================== RCS file: /cvs/poppler/poppler/poppler/Stream.h,v retrieving revision 1.11 diff -u -8 -p -r1.11 Stream.h --- poppler/Stream.h 13 Jan 2007 23:19:21 -0000 1.11 +++ poppler/Stream.h 26 Feb 2007 22:42:34 -0000 @@ -74,16 +74,24 @@ public: // Peek at next char in stream. virtual int lookChar() = 0; // Get next char from stream without using the predictor. // 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); // Get current position in file. virtual int getPos() = 0; // Go to a position in the stream. If is negative, the // position is from the end of the file; otherwise the position is @@ -166,17 +174,19 @@ public: FilterStream(Stream *strA); virtual ~FilterStream(); virtual void close(); virtual int getPos() { return str->getPos(); } virtual void setPos(Guint pos, int dir = 0); virtual BaseStream *getBaseStream() { return str->getBaseStream(); } virtual Dict *getDict() { return str->getDict(); } - + + virtual int getUnfilteredChar () { return str->getUnfilteredChar(); } + virtual void unfilteredReset () { str->unfilteredReset(); } protected: Stream *str; }; //------------------------------------------------------------------------ // ImageStream //------------------------------------------------------------------------ @@ -272,17 +282,19 @@ public: virtual int getChar() { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); } virtual int lookChar() { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); } virtual int getPos() { return bufPos + (bufPtr - buf); } virtual void setPos(Guint pos, int dir = 0); virtual Guint getStart() { return start; } virtual void moveStart(int delta); - + + virtual int getUnfilteredChar () { return getChar(); } + virtual void unfilteredReset () { reset(); } private: GBool fillBuf(); FILE *f; Guint start; GBool limited; Guint length; @@ -314,16 +326,18 @@ public: { return (bufPtr < bufEnd) ? (*bufPtr & 0xff) : EOF; } virtual int getPos() { return (int)(bufPtr - buf); } virtual void setPos(Guint pos, int dir = 0); virtual Guint getStart() { return start; } virtual void moveStart(int delta); virtual void doDecryption(Guchar *fileKey, int keyLength, int objNum, int objGen); + virtual int getUnfilteredChar () { return getChar(); } + virtual void unfilteredReset () { reset (); } private: char *buf; Guint start; Guint length; char *bufEnd; char *bufPtr; GBool needFree; @@ -350,16 +364,18 @@ public: virtual void reset() {} virtual int getChar(); virtual int lookChar(); virtual int getPos() { return str->getPos(); } virtual void setPos(Guint pos, int dir = 0); virtual Guint getStart(); virtual void moveStart(int delta); + virtual int getUnfilteredChar () { return str->getUnfilteredChar(); } + virtual void unfilteredReset () { str->unfilteredReset(); } private: Stream *str; GBool limited; Guint length; }; //------------------------------------------------------------------------ @@ -498,16 +514,17 @@ public: virtual StreamKind getKind() { return strCCITTFax; } virtual void reset(); virtual int getChar() { int c = lookChar(); buf = EOF; return c; } virtual int lookChar(); virtual GooString *getPSFilter(int psLevel, char *indent); virtual GBool isBinary(GBool last = gTrue); + virtual void unfilteredReset (); private: int encoding; // 'K' parameter GBool endOfLine; // 'EndOfLine' parameter GBool byteAlign; // 'EncodedByteAlign' parameter int columns; // 'Columns' parameter int rows; // 'Rows' parameter GBool endOfBlock; // 'EndOfBlock' parameter @@ -569,16 +586,17 @@ public: virtual ~DCTStream(); virtual StreamKind getKind() { return strDCT; } virtual void reset(); virtual int getChar(); virtual int lookChar(); virtual GooString *getPSFilter(int psLevel, char *indent); virtual GBool isBinary(GBool last = gTrue); Stream *getRawStream() { return str; } + virtual void unfilteredReset(); private: GBool progressive; // set if in progressive mode GBool interleaved; // set if in interleaved mode int width, height; // image size int mcuWidth, mcuHeight; // size of min coding unit, in data units int bufWidth, bufHeight; // frameBuf size @@ -672,16 +690,18 @@ public: virtual StreamKind getKind() { return strFlate; } virtual void reset(); virtual int getChar(); virtual int lookChar(); virtual int getRawChar(); virtual GooString *getPSFilter(int psLevel, char *indent); virtual GBool isBinary(GBool last = gTrue); + virtual void unfilteredReset (); + private: StreamPredictor *pred; // predictor Guchar buf[flateWindow]; // output data buffer int index; // current index into output buffer int remain; // number valid bytes in output buffer int codeBuf; // input buffer int codeSize; // number of bits in input buffer