/** <param name="file">Associated file.</param> <param name="dataObject"> <para>Data object associated to the indirect object. It MUST be</para> <list type="bullet"> <item><code>null</code>, if the indirect object is original or free.</item> <item>NOT <code>null</code>, if the indirect object is new and in-use.</item> </list> </param> <param name="xrefEntry">Cross-reference entry associated to the indirect object. If the indirect object is new, its offset field MUST be set to 0.</param> */ internal PdfIndirectObject( File file, PdfDataObject dataObject, XRefEntry xrefEntry ) { this.file = file; this.dataObject = Include(dataObject); this.xrefEntry = xrefEntry; this.original = (xrefEntry.Offset >= 0); this.reference = new PdfReference(this); }
/** <summary>Retrieves the file information.</summary> */ public FileInfo ReadInfo( ) { //TODO:hybrid xref table/stream Version version = Version.Get(parser.RetrieveVersion()); PdfDictionary trailer = null; SortedDictionary<int,XRefEntry> xrefEntries = new SortedDictionary<int,XRefEntry>(); { long sectionOffset = parser.RetrieveXRefOffset(); while(sectionOffset > -1) { // Move to the start of the xref section! parser.Seek(sectionOffset); PdfDictionary sectionTrailer; if(parser.GetToken(1).Equals(Keyword.XRef)) // XRef-table section. { // Looping sequentially across the subsections inside the current xref-table section... while(true) { /* NOTE: Each iteration of this block represents the scanning of one subsection. We get its bounds (first and last object numbers within its range) and then collect its entries. */ // 1. First object number. parser.MoveNext(); if((parser.TokenType == PostScriptParser.TokenTypeEnum.Keyword) && parser.Token.Equals(Keyword.Trailer)) // XRef-table section ended. break; else if(parser.TokenType != PostScriptParser.TokenTypeEnum.Integer) throw new ParseException("Neither object number of the first object in this xref subsection nor end of xref section found.",parser.Position); // Get the object number of the first object in this xref-table subsection! int startObjectNumber = (int)parser.Token; // 2. Last object number. parser.MoveNext(); if(parser.TokenType != PostScriptParser.TokenTypeEnum.Integer) throw new ParseException("Number of entries in this xref subsection not found.", parser.Position); // Get the object number of the last object in this xref-table subsection! int endObjectNumber = (int)parser.Token + startObjectNumber; // 3. XRef-table subsection entries. for( int index = startObjectNumber; index < endObjectNumber; index++ ) { if(xrefEntries.ContainsKey(index)) // Already-defined entry. { // Skip to the next entry! parser.MoveNext(3); continue; } // Get the indirect object offset! int offset = (int)parser.GetToken(1); // Get the object generation number! int generation = (int)parser.GetToken(1); // Get the usage tag! XRefEntry.UsageEnum usage; { string usageToken = (string)parser.GetToken(1); if(usageToken.Equals(Keyword.InUseXrefEntry)) usage = XRefEntry.UsageEnum.InUse; else if(usageToken.Equals(Keyword.FreeXrefEntry)) usage = XRefEntry.UsageEnum.Free; else throw new ParseException("Invalid xref entry.", parser.Position); } // Define entry! xrefEntries[index] = new XRefEntry( index, generation, offset, usage ); } } // Get the previous trailer! sectionTrailer = (PdfDictionary)parser.ParsePdfObject(1); } else // XRef-stream section. { XRefStream stream = (XRefStream)parser.ParsePdfObject(3); // Gets the xref stream skipping the indirect-object header. // XRef-stream subsection entries. foreach(XRefEntry xrefEntry in stream.Values) { if(xrefEntries.ContainsKey(xrefEntry.Number)) // Already-defined entry. continue; // Define entry! xrefEntries[xrefEntry.Number] = xrefEntry; } // Get the previous trailer! sectionTrailer = stream.Header; } if(trailer == null) {trailer = sectionTrailer;} // Get the previous xref-table section's offset! PdfInteger prevXRefOffset = (PdfInteger)sectionTrailer[PdfName.Prev]; sectionOffset = (prevXRefOffset != null ? prevXRefOffset.IntValue : -1); } } return new FileInfo(version, trailer, xrefEntries); }
protected override void WriteIncremental( ) { // 1. Original content (header, body and previous trailer). FileParser parser = file.Reader.Parser; stream.Write(parser.Stream); // 2. Body update (modified indirect objects insertion). XRefEntry xrefStreamEntry; { // 2.1. Content indirect objects. IndirectObjects indirectObjects = file.IndirectObjects; // Create the xref stream! /* NOTE: Incremental xref information structure comprises multiple sections; this update adds a new section. */ XRefStream xrefStream = new XRefStream(file); XRefEntry prevFreeEntry = null; /* NOTE: Extension object streams are necessary to update original object streams whose entries have been modified. */ IDictionary<int,ObjectStream> extensionObjectStreams = new Dictionary<int,ObjectStream>(); foreach(PdfIndirectObject indirectObject in new List<PdfIndirectObject>(indirectObjects.ModifiedObjects.Values)) { prevFreeEntry = AddXRefEntry( indirectObject.XrefEntry, indirectObject, xrefStream, prevFreeEntry, extensionObjectStreams ); } foreach(ObjectStream extensionObjectStream in extensionObjectStreams.Values) { prevFreeEntry = AddXRefEntry( extensionObjectStream.Container.XrefEntry, extensionObjectStream.Container, xrefStream, prevFreeEntry, null ); } if(prevFreeEntry != null) {prevFreeEntry.Offset = 0;} // Links back to the first free object. NOTE: The first entry in the table (object number 0) is always free. // 2.2. XRef stream. /* NOTE: This xref stream indirect object is purposely temporary (i.e. not registered into the file's indirect objects collection). */ new PdfIndirectObject( file, xrefStream, xrefStreamEntry = new XRefEntry(indirectObjects.Count, 0) ); UpdateTrailer(xrefStream.Header, stream); xrefStream.Header[PdfName.Prev] = PdfInteger.Get((int)parser.RetrieveXRefOffset()); AddXRefEntry( xrefStreamEntry, xrefStream.Container, xrefStream, null, null ); } // 3. Tail. WriteTail(xrefStreamEntry.Offset); }
/** <summary>Adds an indirect object entry to the specified xref stream.</summary> <param name="xrefEntry">Indirect object's xref entry.</param> <param name="indirectObject">Indirect object.</param> <param name="xrefStream">XRef stream.</param> <param name="prevFreeEntry">Previous free xref entry.</param> <param name="extensionObjectStreams">Object streams used in incremental updates to extend modified ones.</param> <returns>Current free xref entry.</returns> */ private XRefEntry AddXRefEntry( XRefEntry xrefEntry, PdfIndirectObject indirectObject, XRefStream xrefStream, XRefEntry prevFreeEntry, IDictionary<int,ObjectStream> extensionObjectStreams ) { xrefStream[xrefEntry.Number] = xrefEntry; switch(xrefEntry.Usage) { case XRefEntry.UsageEnum.InUse: { int offset = (int)stream.Length; // Add entry content! indirectObject.WriteTo(stream, file); // Set entry content's offset! xrefEntry.Offset = offset; } break; case XRefEntry.UsageEnum.InUseCompressed: /* NOTE: Serialization is delegated to the containing object stream. */ if(extensionObjectStreams != null) // Incremental update. { int baseStreamNumber = xrefEntry.StreamNumber; PdfIndirectObject baseStreamIndirectObject = file.IndirectObjects[baseStreamNumber]; if(baseStreamIndirectObject.IsOriginal()) // Extension stream needed in order to preserve the original object stream. { // Get the extension object stream associated to the original object stream! ObjectStream extensionObjectStream; if(!extensionObjectStreams.TryGetValue(baseStreamNumber, out extensionObjectStream)) { file.Register(extensionObjectStream = new ObjectStream()); // Link the extension to the base object stream! extensionObjectStream.BaseStream = (ObjectStream)baseStreamIndirectObject.DataObject; extensionObjectStreams[baseStreamNumber] = extensionObjectStream; } // Insert the data object into the extension object stream! extensionObjectStream[xrefEntry.Number] = indirectObject.DataObject; // Update the data object's xref entry! xrefEntry.StreamNumber = extensionObjectStream.Reference.ObjectNumber; xrefEntry.Offset = XRefEntry.UndefinedOffset; // Internal object index unknown (to set on object stream serialization -- see ObjectStream). } } break; case XRefEntry.UsageEnum.Free: if(prevFreeEntry != null) {prevFreeEntry.Offset = xrefEntry.Number;} // Object number of the next free object. prevFreeEntry = xrefEntry; break; default: throw new NotSupportedException(); } return prevFreeEntry; }
protected override void WriteStandard( ) { // 1. Header [PDF:1.6:3.4.1]. WriteHeader(); // 2. Body [PDF:1.6:3.4.2,3,7]. XRefEntry xrefStreamEntry; { // 2.1. Content indirect objects. IndirectObjects indirectObjects = file.IndirectObjects; // Create the xref stream indirect object! /* NOTE: Standard xref information structure comprises just one section; the xref stream is generated on-the-fly and kept volatile not to interfere with the existing file structure. */ /* NOTE: This xref stream indirect object is purposely temporary (i.e. not registered into the file's indirect objects collection). */ XRefStream xrefStream = new XRefStream(file); new PdfIndirectObject( file, xrefStream, xrefStreamEntry = new XRefEntry(indirectObjects.Count, 0) ); XRefEntry prevFreeEntry = null; foreach(PdfIndirectObject indirectObject in indirectObjects) { prevFreeEntry = AddXRefEntry( indirectObject.XrefEntry, indirectObject, xrefStream, prevFreeEntry, null ); } prevFreeEntry.Offset = 0; // Links back to the first free object. NOTE: The first entry in the table (object number 0) is always free. // 2.2. XRef stream. UpdateTrailer(xrefStream.Header, stream); AddXRefEntry( xrefStreamEntry, xrefStream.Container, xrefStream, null, null ); } // 3. Tail. WriteTail(xrefStreamEntry.Offset); }
protected override void WriteIncremental( ) { // 1. Original content (header, body and previous trailer). FileParser parser = file.Reader.Parser; stream.Write(parser.Stream); // 2. Body update (modified indirect objects insertion). XRefEntry xrefStreamEntry; { // Create the xref stream! /* * NOTE: Incremental xref information structure comprises multiple sections; this update adds * a new section. */ XRefStream xrefStream = new XRefStream(file); // 2.1. Indirect objects. IndirectObjects indirectObjects = file.IndirectObjects; // 2.1.1. Modified indirect objects serialization. XRefEntry prevFreeEntry = null; /* * NOTE: Any uncompressed indirect object will be compressed. */ ObjectStream objectStream = null; /* * NOTE: Any previously-compressed indirect object will have its original object stream * updated through a new extension object stream. */ IDictionary <int, ObjectStream> extensionObjectStreams = new Dictionary <int, ObjectStream>(); int indirectObjectsPrecompressCount = indirectObjects.Count; foreach (PdfIndirectObject indirectObject in new List <PdfIndirectObject>(indirectObjects.ModifiedObjects.Values)) { if (indirectObject.IsCompressible()) { if (objectStream == null || objectStream.Count >= ObjectStreamMaxEntryCount) { file.Register(objectStream = new ObjectStream()); } indirectObject.Compress(objectStream); } prevFreeEntry = AddXRefEntry( indirectObject, xrefStream, prevFreeEntry, extensionObjectStreams ); } // 2.1.2. Additional object streams serialization. for (int index = indirectObjectsPrecompressCount, limit = indirectObjects.Count; index < limit; index++) { prevFreeEntry = AddXRefEntry( indirectObjects[index], xrefStream, prevFreeEntry, null ); } if (prevFreeEntry != null) { prevFreeEntry.Offset = 0; // Links back to the first free object. NOTE: The first entry in the table (object number 0) is always free. } // 2.2. XRef stream. UpdateTrailer(xrefStream.Header, stream); xrefStream.Header[PdfName.Prev] = PdfInteger.Get((int)parser.RetrieveXRefOffset()); AddXRefEntry( /* * NOTE: This xref stream indirect object is purposely temporary (i.e. not registered into * the file's indirect objects collection). */ new PdfIndirectObject( file, xrefStream, xrefStreamEntry = new XRefEntry(indirectObjects.Count, 0, (int)stream.Length, XRefEntry.UsageEnum.InUse) ), xrefStream, null, null ); } // 3. Tail. WriteTail(xrefStreamEntry.Offset); }
/** * <summary>Adds an indirect object entry to the specified xref stream.</summary> * <param name="indirectObject">Indirect object.</param> * <param name="xrefStream">XRef stream.</param> * <param name="prevFreeEntry">Previous free xref entry.</param> * <param name="extensionObjectStreams">Object streams used in incremental updates to extend * modified ones.</param> * <returns>Current free xref entry.</returns> */ private XRefEntry AddXRefEntry( PdfIndirectObject indirectObject, XRefStream xrefStream, XRefEntry prevFreeEntry, IDictionary <int, ObjectStream> extensionObjectStreams ) { XRefEntry xrefEntry = indirectObject.XrefEntry; // Add the entry to the xref stream! xrefStream[xrefEntry.Number] = xrefEntry; // Serialize the entry contents! switch (xrefEntry.Usage) { case XRefEntry.UsageEnum.InUse: { int offset = (int)stream.Length; // Add entry content! indirectObject.WriteTo(stream, file); // Set entry content's offset! xrefEntry.Offset = offset; } break; case XRefEntry.UsageEnum.InUseCompressed: /* * NOTE: Serialization is delegated to the containing object stream. */ if (extensionObjectStreams != null) // Incremental update. { int baseStreamNumber = xrefEntry.StreamNumber; PdfIndirectObject baseStreamIndirectObject = file.IndirectObjects[baseStreamNumber]; if (baseStreamIndirectObject.IsOriginal()) // Extension stream needed in order to preserve the original object stream. { // Get the extension object stream associated to the original object stream! ObjectStream extensionObjectStream; if (!extensionObjectStreams.TryGetValue(baseStreamNumber, out extensionObjectStream)) { file.Register(extensionObjectStream = new ObjectStream()); // Link the extension to the base object stream! extensionObjectStream.BaseStream = (ObjectStream)baseStreamIndirectObject.DataObject; extensionObjectStreams[baseStreamNumber] = extensionObjectStream; } // Insert the data object into the extension object stream! extensionObjectStream[xrefEntry.Number] = indirectObject.DataObject; // Update the data object's xref entry! xrefEntry.StreamNumber = extensionObjectStream.Reference.ObjectNumber; xrefEntry.Offset = XRefEntry.UndefinedOffset; // Internal object index unknown (to set on object stream serialization -- see ObjectStream). } } break; case XRefEntry.UsageEnum.Free: if (prevFreeEntry != null) { prevFreeEntry.Offset = xrefEntry.Number; } // Object number of the next free object. prevFreeEntry = xrefEntry; break; default: throw new NotSupportedException(); } return(prevFreeEntry); }
protected override void WriteStandard( ) { // 1. Header [PDF:1.6:3.4.1]. WriteHeader(); // 2. Body [PDF:1.6:3.4.2,3,7]. XRefEntry xrefStreamEntry; { // Create the xref stream! /* * NOTE: Standard xref information structure comprises just one section; the xref stream is * generated on-the-fly and kept volatile not to interfere with the existing file structure. */ XRefStream xrefStream = new XRefStream(file); // 2.1. Indirect objects. IndirectObjects indirectObjects = file.IndirectObjects; // Indirect objects serialization. XRefEntry prevFreeEntry = null; ObjectStream objectStream = null; foreach (PdfIndirectObject indirectObject in indirectObjects) { if (indirectObject.IsCompressible()) { if (objectStream == null || objectStream.Count >= ObjectStreamMaxEntryCount) { file.Register(objectStream = new ObjectStream()); } indirectObject.Compress(objectStream); } prevFreeEntry = AddXRefEntry( indirectObject, xrefStream, prevFreeEntry, null ); } prevFreeEntry.Offset = 0; // Links back to the first free object. NOTE: The first entry in the table (object number 0) is always free. // 2.2. XRef stream. UpdateTrailer(xrefStream.Header, stream); AddXRefEntry( /* * NOTE: This xref stream indirect object is purposely temporary (i.e. not registered into * the file's indirect objects collection). */ new PdfIndirectObject( file, xrefStream, xrefStreamEntry = new XRefEntry(indirectObjects.Count, 0, (int)stream.Length, XRefEntry.UsageEnum.InUse) ), xrefStream, null, null ); } // 3. Tail. WriteTail(xrefStreamEntry.Offset); }
protected override void WriteIncremental( ) { try { // 1. Original content (head, body and previous trailer). Parser parser = file.Reader.Parser; stream.Write(parser.Stream); // 2. Body update (modified indirect objects insertion). XRefEntry xrefStreamEntry; { // 2.1. Content indirect objects. IndirectObjects indirectObjects = file.IndirectObjects; // Create the xref stream indirect object! /* * NOTE: Incremental xref table comprises multiple sections each one composed by multiple subsections; * this update adds a new section. */ /* * NOTE: This xref stream indirect object is purposely temporary (i.e. not registered into the file's * indirect objects collection). */ XRefStream xrefStream; PdfIndirectObject xrefStreamIndirectObject = new PdfIndirectObject( file, xrefStream = new XRefStream(file), xrefStreamEntry = new XRefEntry(indirectObjects.Count, 0, 0, XRefEntry.UsageEnum.InUse) ); XRefEntry prevFreeEntry = null; foreach (PdfIndirectObject indirectObject in indirectObjects.ModifiedObjects.Values) { prevFreeEntry = AddXRefEntry( indirectObject.XrefEntry, indirectObject, xrefStream, prevFreeEntry ); } if (prevFreeEntry != null) { prevFreeEntry.Offset = 0; } // Linking back to the first free object. NOTE: The first entry in the table (object number 0) is always free. // 2.2. XRef stream. xrefStream.Header[PdfName.Prev] = new PdfInteger((int)parser.RetrieveXRefOffset()); AddXRefEntry( xrefStreamEntry, xrefStreamIndirectObject, xrefStream, null ); } // 3. Tail. WriteTail(xrefStreamEntry.Offset); } catch (Exception e) { throw new Exception("Incremental writing failed.", e); } }
protected override void WriteStandard( ) { try { // 1. Header [PDF:1.6:3.4.1]. WriteHeader(); // 2. Body [PDF:1.6:3.4.2,3,7]. XRefEntry xrefStreamEntry; { // 2.1. Content indirect objects. IndirectObjects indirectObjects = file.IndirectObjects; // Create the xref stream indirect object! /* * NOTE: A standard xref stream comprises just one section composed by just one subsection. * The xref stream is generated on-the-fly and kept volatile not to interfere with the existing * file structure. */ /* * NOTE: This xref stream indirect object is purposely temporary (i.e. not registered into the file's * indirect objects collection). */ XRefStream xrefStream; PdfIndirectObject xrefStreamIndirectObject = new PdfIndirectObject( file, xrefStream = new XRefStream(file), xrefStreamEntry = new XRefEntry(indirectObjects.Count, 0, 0, XRefEntry.UsageEnum.InUse) ); XRefEntry prevFreeEntry = null; foreach (PdfIndirectObject indirectObject in indirectObjects) { PdfIndirectObject actualIndirectObject; if (indirectObject.DataObject is XRefStream) { /* * NOTE: Existing xref streams MUST be suppressed, * temporarily replacing them with free entries. */ actualIndirectObject = new PdfIndirectObject( file, null, new XRefEntry( indirectObject.Reference.ObjectNumber, XRefEntry.GenerationUnreusable, 0, XRefEntry.UsageEnum.Free ) ); } else { actualIndirectObject = indirectObject; } prevFreeEntry = AddXRefEntry( (XRefEntry)actualIndirectObject.XrefEntry.Clone(), // NOTE: Xref entry is cloned to preserve the original one. actualIndirectObject, xrefStream, prevFreeEntry ); } prevFreeEntry.Offset = 0; // Linking back to the first free object. NOTE: The first entry in the table (object number 0) is always free. // 2.2. XRef stream. AddXRefEntry( xrefStreamEntry, xrefStreamIndirectObject, xrefStream, null ); } // 3. Tail. WriteTail(xrefStreamEntry.Offset); } catch (Exception e) { throw new Exception("Standard writing failed.", e); } }