Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 64 additions & 8 deletions src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
throw new PdfReaderException("PdfReader needs a stream that supports the Length property.", ex);
}

if (openMode == PdfDocumentOpenMode.ModifyIncremental)
{
// An incremental update writes the bytes of the original file unchanged before it appends
// the modified objects. The stream is not necessarily available anymore when the document
// is saved, therefore the bytes are kept in memory.
_document.OriginalBytes = ReadAllBytes(stream);
}

// Get file version.
byte[] header = new byte[1024];
stream.Position = 0;
Expand Down Expand Up @@ -440,7 +448,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
reachables = document.xrefTable.AllXRefs;
document.xrefTable.CheckConsistence();
#endif
if (openMode == PdfDocumentOpenMode.Modify)
if (openMode is PdfDocumentOpenMode.Modify or PdfDocumentOpenMode.ModifyIncremental)
{
// Create new or change existing document IDs.
if (_document.Internals.SecondDocumentID == "")
Expand All @@ -455,20 +463,42 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
// Change modification date.
_document.Info.ModificationDate = DateTimeOffset.Now;

// Remove all unreachable objects.
int removed = _document.IrefTable.Compact();
if (removed != 0)
if (openMode == PdfDocumentOpenMode.ModifyIncremental)
{
//Debug.WriteLine("Number of deleted unreachable objects: " + removed);
PdfSharpLogHost.PdfReadingLogger.LogInformation("Number of deleted unreachable objects: {Removed}", removed);
// An incremental update rewrites only the objects that were modified. The document
// information dictionary was just modified, so it must be written again.
_document.MarkAsModified(_document.Info);
}
else
{
// Remove all unreachable objects.
// Not for an incremental update: the objects of the original file are written unchanged
// and may be referenced by an earlier revision of the document.
int removed = _document.IrefTable.Compact();
if (removed != 0)
{
//Debug.WriteLine("Number of deleted unreachable objects: " + removed);
PdfSharpLogHost.PdfReadingLogger.LogInformation("Number of deleted unreachable objects: {Removed}", removed);
}
}

// Force flattening of page tree.
_document.Pages.FlattenPageTree();

_document.IrefTable.CheckConsistence();
_document.IrefTable.Renumber();
_document.IrefTable.CheckConsistence();
if (openMode == PdfDocumentOpenMode.Modify)
{
// Renumbering the objects would invalidate the cross-reference table of the original
// file, which is kept unchanged by an incremental update.
_document.IrefTable.Renumber();
_document.IrefTable.CheckConsistence();
}
else
{
// Remember the objects of the original file. An incremental update writes all objects
// that are not contained here, because they were created after the document was read.
_document.OriginalObjectIDs = [.. _document.IrefTable.AllObjectIDs];
}
}
else if (openMode == PdfDocumentOpenMode.Import)
{
Expand Down Expand Up @@ -496,6 +526,32 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode
return _document;
}

/// <summary>
/// Reads the whole stream from its beginning and restores the original stream position.
/// </summary>
static byte[] ReadAllBytes(Stream stream)
{
var position = stream.Position;
try
{
stream.Position = 0;
var bytes = new byte[stream.Length];
var offset = 0;
while (offset < bytes.Length)
{
var read = stream.Read(bytes, offset, bytes.Length - offset);
if (read <= 0)
throw new PdfReaderException("Unexpected end of the stream to be read.");
offset += read;
}
return bytes;
}
finally
{
stream.Position = position;
}
}

/// <summary>
/// Ensures that all references in all objects refer to the actual object or to the null object (see ShouldUpdateReference method).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,17 @@ public enum PdfDocumentOpenMode
/// </summary>
[Obsolete("InformationOnly is not implemented, use Import instead.")]
InformationOnly,

// Note: New members must be appended here to keep the numeric values of the existing members stable.

/// <summary>
/// Like <see cref="Modify"/>, but the object numbering of the original file is preserved: unreachable
/// objects are not removed and the cross-reference table is not renumbered. This is a prerequisite for
/// an append-only incremental update, where the bytes of the original file are written unchanged and
/// every object must keep the object number it has in that file.
/// A document opened in this mode can only be saved with
/// <see cref="PdfDocument.SaveIncremental(System.IO.Stream, bool)"/> and its overloads.
/// </summary>
ModifyIncremental,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ internal async Task AddSignatureComponentsAsync() // #US321 TODO Use appropriate
}

acroForm.Fields.Elements.Add(signatureField);

// The page, the array of annotations (which may be an indirect object of its own), the
// interactive form and the catalog are objects of the original file that are modified here.
// An incremental update must write them again.
Document.MarkAsModified(page);
if (annotations != null)
Document.MarkAsModified(annotations);
Document.MarkAsModified(acroForm);
Document.MarkAsModified(acroForm.Fields);
Document.MarkAsModified(catalog);
}

PdfFormSignatureField GetSignatureField(PdfSignature signatureDic) // #US321 TODO Use appropriate classes.
Expand Down
Loading