diff --git a/PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs b/PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs
index 522415dd..ba6ae08c 100644
--- a/PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs
+++ b/PdfSharpCore/Pdf.AcroForms/PdfAcroField.cs
@@ -300,6 +300,10 @@ public sealed class PdfAcroFieldCollection : PdfArray
: base(array)
{ }
+ PdfAcroFieldCollection(PdfDocument document)
+ : base(document)
+ { }
+
///
/// Gets the names of all fields in the collection.
///
@@ -553,7 +557,27 @@ public class Keys : KeysBase
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string Q = "/Q";
- // ReSharper restore InconsistentNaming
+ ///
+ /// (Optional) The type of PDF object that this dictionary describes; if present,
+ /// must be Sig for a signature dictionary.
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)]
+ public const string Type = "/Type";
+
+ ///
+ ///
+ ///
+ [KeyInfo(KeyType.Name | KeyType.Required)]
+ public const string Subtype = "/Subtype";
+
+ ///
+ ///
+ ///
+ [KeyInfo(KeyType.Rectangle | KeyType.Required)]
+ public const string Rect = "/Rect";
+
+ [KeyInfo(KeyType.Rectangle | KeyType.Required)]
+ public const string P = "/P";
}
}
}
diff --git a/PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs b/PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs
index b7f6a87a..3c562253 100644
--- a/PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs
+++ b/PdfSharpCore/Pdf.AcroForms/PdfSignatureField.cs
@@ -27,6 +27,11 @@
// DEALINGS IN THE SOFTWARE.
#endregion
+using PdfSharpCore.Drawing;
+using PdfSharpCore.Pdf.Annotations;
+using PdfSharpCore.Pdf.Signatures;
+using System;
+
namespace PdfSharpCore.Pdf.AcroForms
{
///
@@ -34,29 +39,145 @@ namespace PdfSharpCore.Pdf.AcroForms
///
public sealed class PdfSignatureField : PdfAcroField
{
+ private bool visible;
+
+ public string Reason
+ {
+ get
+ {
+ return Elements.GetDictionary(Keys.V).Elements.GetString(Keys.Reason);
+ }
+ set
+ {
+ Elements.GetDictionary(Keys.V).Elements[Keys.Reason] = new PdfString(value);
+ }
+ }
+
+ public string Location
+ {
+ get
+ {
+ return Elements.GetDictionary(Keys.V).Elements.GetString(Keys.Location);
+ }
+ set
+ {
+ Elements.GetDictionary(Keys.V).Elements[Keys.Location] = new PdfString(value);
+ }
+ }
+
+ public PdfItem Contents
+ {
+ get
+ {
+ return Elements.GetDictionary(Keys.V).Elements[Keys.Contents];
+ }
+ set
+ {
+ Elements.GetDictionary(Keys.V).Elements.Add(Keys.Contents, value);
+ }
+ }
+
+
+ public PdfItem ByteRange
+ {
+ get
+ {
+ return Elements.GetDictionary(Keys.V).Elements[Keys.ByteRange];
+ }
+ set
+ {
+ Elements.GetDictionary(Keys.V).Elements.Add(Keys.ByteRange, value);
+ }
+ }
+
+
+ public PdfRectangle Rectangle
+ {
+ get
+ {
+ return (PdfRectangle)Elements[Keys.Rect];
+ }
+ set
+ {
+ Elements.Add(Keys.Rect, value);
+ this.visible = !(value.X1 + value.X2 + value.Y1 + value.Y2 == 0);
+
+ }
+ }
+
+
+ public ISignatureAppearanceHandler AppearanceHandler { get; internal set; }
+
///
/// Initializes a new instance of PdfSignatureField.
///
- internal PdfSignatureField(PdfDocument document)
- : base(document)
- { }
+ internal PdfSignatureField(PdfDocument document) : base(document)
+ {
+
+
+ Elements.Add(Keys.FT, new PdfName("/Sig"));
+ Elements.Add(Keys.T, new PdfString("Signature1"));
+ Elements.Add(Keys.Ff, new PdfInteger(132));
+ Elements.Add(Keys.DR, new PdfDictionary());
+ Elements.Add(Keys.Type, new PdfName("/Annot"));
+ Elements.Add(Keys.Subtype, new PdfName("/Widget"));
+ Elements.Add(Keys.P, document.Pages[0]);
+
+
+ PdfDictionary sign = new PdfDictionary(document);
+ sign.Elements.Add(Keys.Type, new PdfName("/Sig"));
+ sign.Elements.Add(Keys.Filter, new PdfName("/Adobe.PPKLite"));
+ sign.Elements.Add(Keys.SubFilter, new PdfName("/adbe.pkcs7.detached"));
+ sign.Elements.Add(Keys.M, new PdfDate(DateTime.Now));
+
+ document._irefTable.Add(sign);
+ document._irefTable.Add(this);
+
+ Elements.Add(Keys.V, sign);
+
+ }
internal PdfSignatureField(PdfDictionary dict)
: base(dict)
{ }
+
+ internal override void PrepareForSave()
+ {
+ if (!this.visible)
+ return;
+
+ if (this.AppearanceHandler == null)
+ throw new Exception("AppearanceHandler is null");
+
+
+
+ PdfRectangle rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect);
+ XForm form = new XForm(this._document, rect.Size);
+ XGraphics gfx = XGraphics.FromForm(form);
+
+ this.AppearanceHandler.DrawAppearance(gfx, rect.ToXRect());
+
+ form.DrawingFinished();
+
+ // Get existing or create new appearance dictionary
+ PdfDictionary ap = Elements[PdfAnnotation.Keys.AP] as PdfDictionary;
+ if (ap == null)
+ {
+ ap = new PdfDictionary(this._document);
+ Elements[PdfAnnotation.Keys.AP] = ap;
+ }
+
+ // Set XRef to normal state
+ ap.Elements["/N"] = form.PdfForm.Reference;
+ }
+
///
/// Predefined keys of this dictionary.
/// The description comes from PDF 1.4 Reference.
///
public new class Keys : PdfAcroField.Keys
- {
- ///
- /// (Optional) The type of PDF object that this dictionary describes; if present,
- /// must be Sig for a signature dictionary.
- ///
- [KeyInfo(KeyType.Name | KeyType.Optional)]
- public const string Type = "/Type";
+ {
///
/// (Required; inheritable) The name of the signature handler to be used for
@@ -113,6 +234,12 @@ internal PdfSignatureField(PdfDictionary dict)
[KeyInfo(KeyType.TextString | KeyType.Optional)]
public const string Reason = "/Reason";
+ ///
+ /// (Optional)
+ ///
+ [KeyInfo(KeyType.TextString | KeyType.Optional)]
+ public const string ContactInfo = "/ContactInfo";
+
///
/// Gets the KeysMeta for these keys.
///
diff --git a/PdfSharpCore/Pdf.Advanced/PdfContents.cs b/PdfSharpCore/Pdf.Advanced/PdfContents.cs
index c1941109..0d4ffe7d 100644
--- a/PdfSharpCore/Pdf.Advanced/PdfContents.cs
+++ b/PdfSharpCore/Pdf.Advanced/PdfContents.cs
@@ -200,7 +200,7 @@ internal override void WriteObject(PdfWriter writer)
{
// Save two bytes in PDF stream...
if (Elements.Count == 1)
- Elements[0].WriteObject(writer);
+ Elements[0].Write(writer);
else
base.WriteObject(writer);
}
diff --git a/PdfSharpCore/Pdf.Advanced/PdfInternals.cs b/PdfSharpCore/Pdf.Advanced/PdfInternals.cs
index b921ccbd..a4a43157 100644
--- a/PdfSharpCore/Pdf.Advanced/PdfInternals.cs
+++ b/PdfSharpCore/Pdf.Advanced/PdfInternals.cs
@@ -268,7 +268,7 @@ public void WriteObject(Stream stream, PdfItem item)
// Never write an encrypted object
PdfWriter writer = new PdfWriter(stream, null);
writer.Options = PdfWriterOptions.OmitStream;
- item.WriteObject(writer);
+ item.Write(writer);
}
///
diff --git a/PdfSharpCore/Pdf.IO/PdfWriter.cs b/PdfSharpCore/Pdf.IO/PdfWriter.cs
index bec19e4d..e397af95 100644
--- a/PdfSharpCore/Pdf.IO/PdfWriter.cs
+++ b/PdfSharpCore/Pdf.IO/PdfWriter.cs
@@ -199,7 +199,7 @@ public void Write(PdfString value)
PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask);
string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ?
PdfEncoders.ToStringLiteral(value.Value, encoding, SecurityHandler) :
- PdfEncoders.ToHexStringLiteral(value.Value, encoding, SecurityHandler);
+ PdfEncoders.ToHexStringLiteral(value.Value, encoding, SecurityHandler, value.PaddingLeft);
WriteRaw(pdf);
_lastCat = CharCat.Delimiter;
diff --git a/PdfSharpCore/Pdf.Internal/PdfEncoders.cs b/PdfSharpCore/Pdf.Internal/PdfEncoders.cs
index 7c050919..bd7d2b97 100644
--- a/PdfSharpCore/Pdf.Internal/PdfEncoders.cs
+++ b/PdfSharpCore/Pdf.Internal/PdfEncoders.cs
@@ -245,9 +245,9 @@ public static string ToStringLiteral(byte[] bytes, bool unicode, PdfStandardSecu
///
/// Converts a raw string into a raw hexadecimal string literal, possibly encrypted.
///
- public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler)
+ public static string ToHexStringLiteral(string text, PdfStringEncoding encoding, PdfStandardSecurityHandler securityHandler, int paddingLeft)
{
- if (String.IsNullOrEmpty(text))
+ if (String.IsNullOrEmpty(text) && paddingLeft == 0)
return "<>";
byte[] bytes;
@@ -274,6 +274,13 @@ public static string ToHexStringLiteral(string text, PdfStringEncoding encoding,
throw new NotImplementedException(encoding.ToString());
}
+ if (bytes.Length < paddingLeft)
+ {
+ byte[] tmp = new byte[paddingLeft];
+ Array.Copy(bytes, tmp, bytes.Length);
+ bytes = tmp;
+ }
+
byte[] agTemp = FormatStringLiteral(bytes, encoding == PdfStringEncoding.Unicode, true, true, securityHandler);
return RawEncoding.GetString(agTemp, 0, agTemp.Length);
}
diff --git a/PdfSharpCore/Pdf.Signatures/DefaultAppearanceHandler.cs b/PdfSharpCore/Pdf.Signatures/DefaultAppearanceHandler.cs
new file mode 100644
index 00000000..e700ee7c
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/DefaultAppearanceHandler.cs
@@ -0,0 +1,32 @@
+using System;
+using PdfSharpCore.Drawing;
+using PdfSharpCore.Drawing.Layout;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ internal class DefaultAppearanceHandler : ISignatureAppearanceHandler
+ {
+ public string Location { get; set; }
+ public string Reason { get; set; }
+ public string Signer { get; set; }
+
+
+ public void DrawAppearance(XGraphics gfx, XRect rect)
+ {
+ var backColor = XColor.Empty;
+ var defaultText = string.Format("Signed by: {0}\nLocation: {1}\nReason: {2}\nDate: {3}", Signer, Location, Reason, DateTime.Now);
+
+ XFont font = new XFont("Verdana", 7, XFontStyle.Regular);
+
+ XTextFormatter txtFormat = new XTextFormatter(gfx);
+
+ var currentPosition = new XPoint(0, 0);
+
+ txtFormat.DrawString(defaultText,
+ font,
+ new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)),
+ new XRect(currentPosition.X, currentPosition.Y, rect.Width - currentPosition.X, rect.Height),
+ XStringFormats.TopLeft);
+ }
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/DefaultSigner.cs b/PdfSharpCore/Pdf.Signatures/DefaultSigner.cs
new file mode 100644
index 00000000..920ac48b
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/DefaultSigner.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography.Pkcs;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ public class DefaultSigner : ISigner
+ {
+ public X509Certificate2 Certificate { get; private set; }
+
+ public DefaultSigner(X509Certificate2 Certificate)
+ {
+ this.Certificate = Certificate;
+ }
+
+ public byte[] GetSignedCms(Stream stream)
+ {
+ var range = new byte[stream.Length];
+
+ stream.Position = 0;
+ stream.Read(range, 0, range.Length);
+
+
+
+ var contentInfo = new ContentInfo(range);
+
+ SignedCms signedCms = new SignedCms(contentInfo, true);
+ CmsSigner signer = new CmsSigner(Certificate);
+ signer.UnsignedAttributes.Add(new Pkcs9SigningTime());
+
+ signedCms.ComputeSignature(signer, true);
+ var bytes = signedCms.Encode();
+
+ return bytes;
+ }
+
+
+
+ public string GetName()
+ {
+ return Certificate.GetNameInfo(X509NameType.SimpleName, false);
+ }
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/ISignatureAppearanceHandler.cs b/PdfSharpCore/Pdf.Signatures/ISignatureAppearanceHandler.cs
new file mode 100644
index 00000000..2bdb99c8
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/ISignatureAppearanceHandler.cs
@@ -0,0 +1,9 @@
+using PdfSharpCore.Drawing;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ public interface ISignatureAppearanceHandler
+ {
+ void DrawAppearance(XGraphics gfx, XRect rect);
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/ISigner.cs b/PdfSharpCore/Pdf.Signatures/ISigner.cs
new file mode 100644
index 00000000..8885be23
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/ISigner.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ public interface ISigner
+ {
+ byte[] GetSignedCms(Stream stream);
+
+ string GetName();
+
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/PdfSignatureHandler.cs b/PdfSharpCore/Pdf.Signatures/PdfSignatureHandler.cs
new file mode 100644
index 00000000..03bdd1a1
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/PdfSignatureHandler.cs
@@ -0,0 +1,138 @@
+using PdfSharpCore.Pdf;
+using PdfSharpCore.Pdf.AcroForms;
+using PdfSharpCore.Pdf.Advanced;
+using PdfSharpCore.Pdf.Annotations;
+using PdfSharpCore.Pdf.Signatures;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography.Pkcs;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using static PdfSharpCore.Pdf.AcroForms.PdfAcroField;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ public class IntEventArgs : EventArgs { public int Value { get; set; } }
+
+ ///
+ /// Handles the signature
+ ///
+ public class PdfSignatureHandler
+ {
+
+ private PositionTracker contentsTraker;
+ private PositionTracker rangeTracker;
+ private int? maximumSignatureLength;
+ private const int byteRangePaddingLength = 36; // the place big enough required to replace [0 0 0 0] with the correct value
+
+ public event EventHandler SignatureSizeComputed = (s, e) => { };
+
+ public PdfDocument Document { get; private set; }
+ public PdfSignatureOptions Options { get; private set; }
+ private ISigner signer { get; set; }
+
+ public void AttachToDocument(PdfDocument documentToSign)
+ {
+ this.Document = documentToSign;
+ this.Document.BeforeSave += AddSignatureComponents;
+ this.Document.AfterSave += ComputeSignatureAndRange;
+
+ if (!maximumSignatureLength.HasValue)
+ {
+ maximumSignatureLength = signer.GetSignedCms(new MemoryStream(new byte[] { 0})).Length;
+ SignatureSizeComputed(this, new IntEventArgs() { Value = maximumSignatureLength.Value });
+ }
+ }
+
+ public PdfSignatureHandler(ISigner signer, int? signatureMaximumLength, PdfSignatureOptions options)
+ {
+ this.signer = signer;
+
+
+ this.maximumSignatureLength = signatureMaximumLength;
+ this.Options = options;
+ }
+
+ private void ComputeSignatureAndRange(object sender, PdfDocumentEventArgs e)
+ {
+ var writer = e.Writer;
+ writer.Stream.Position = rangeTracker.Start;
+ var rangeArray = new PdfArray(new PdfInteger(0),
+ new PdfInteger((int)contentsTraker.Start),
+ new PdfInteger((int)contentsTraker.End),
+ new PdfInteger((int)(writer.Stream.Length - contentsTraker.End)));
+ rangeArray.Write(writer);
+
+ var rangeToSign = GetRangeToSign(writer.Stream);
+
+ var signature = signer.GetSignedCms(rangeToSign);
+ if (signature.Length > maximumSignatureLength)
+ throw new Exception("The signature length is bigger that the approximation made.");
+
+ var hexFormated = Encoding.Default.GetBytes(FormatHex(signature));
+
+ writer.Stream.Position = contentsTraker.Start+1;
+ writer.Write(hexFormated);
+ }
+
+ string FormatHex(byte[] bytes)
+ {
+ var retval = new StringBuilder();
+
+ for (int idx = 0; idx < bytes.Length; idx++)
+ retval.AppendFormat("{0:x2}", bytes[idx]);
+
+ return retval.ToString();
+ }
+
+ private RangedStream GetRangeToSign(Stream stream)
+ {
+ return new RangedStream(stream, new List()
+ {
+ new RangedStream.Range(0, contentsTraker.Start),
+ new RangedStream.Range(contentsTraker.End, stream.Length - contentsTraker.End)
+ });
+
+ }
+
+ private void AddSignatureComponents(object sender, EventArgs e)
+ {
+ var catalog = Document.Catalog;
+
+ if (catalog.AcroForm == null)
+ catalog.AcroForm = new PdfAcroForm(Document);
+
+ catalog.AcroForm.Elements.Add(PdfAcroForm.Keys.SigFlags, new PdfInteger(3));
+
+ var signature = new PdfSignatureField(Document);
+
+ var paddedContents = new PdfString("", PdfStringFlags.HexLiteral, maximumSignatureLength.Value);
+ var paddedRange = new PdfArray(Document, byteRangePaddingLength, new PdfInteger(0), new PdfInteger(0), new PdfInteger(0), new PdfInteger(0));
+
+ signature.Contents = paddedContents;
+ signature.ByteRange = paddedRange;
+ signature.Reason = Options.Reason;
+ signature.Location = Options.Location;
+ signature.Rectangle = new PdfRectangle(Options.Rectangle);
+ signature.AppearanceHandler = Options.AppearanceHandler ?? new DefaultAppearanceHandler()
+ {
+ Location = Options.Location,
+ Reason = Options.Reason,
+ Signer = signer.GetName()
+ };
+ signature.PrepareForSave();
+
+ this.contentsTraker = new PositionTracker(paddedContents);
+ this.rangeTracker = new PositionTracker(paddedRange);
+
+ if (!Document.Pages[0].Elements.ContainsKey(PdfPage.Keys.Annots))
+ Document.Pages[0].Elements.Add(PdfPage.Keys.Annots, new PdfArray(Document));
+
+ (Document.Pages[0].Elements[PdfPage.Keys.Annots] as PdfArray).Elements.Add(signature);
+
+ catalog.AcroForm.Fields.Elements.Add(signature);
+
+ }
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/PdfSignatureOptions.cs b/PdfSharpCore/Pdf.Signatures/PdfSignatureOptions.cs
new file mode 100644
index 00000000..7333ba48
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/PdfSignatureOptions.cs
@@ -0,0 +1,16 @@
+using PdfSharpCore.Drawing;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ public class PdfSignatureOptions
+ {
+ public ISignatureAppearanceHandler AppearanceHandler { get; set; }
+ public string ContactInfo { get; set; }
+ public string Location { get; set; }
+ public string Reason { get; set; }
+ public XRect Rectangle { get; set; }
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/PositionTracker.cs b/PdfSharpCore/Pdf.Signatures/PositionTracker.cs
new file mode 100644
index 00000000..dd2406fe
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/PositionTracker.cs
@@ -0,0 +1,18 @@
+namespace PdfSharpCore.Pdf.Signatures
+{
+ internal class PositionTracker
+ {
+ public PdfItem Item { get; private set; }
+ public long Start { get; private set; }
+ public long End { get; private set; }
+
+ public PositionTracker(PdfItem item)
+ {
+ Item = item;
+ Item.BeforeWrite += (s, e) =>
+ this.Start = e.Position;
+ Item.AfterWrite += (s, e) =>
+ this.End = e.Position;
+ }
+ }
+}
diff --git a/PdfSharpCore/Pdf.Signatures/RangedStream.cs b/PdfSharpCore/Pdf.Signatures/RangedStream.cs
new file mode 100644
index 00000000..9000f419
--- /dev/null
+++ b/PdfSharpCore/Pdf.Signatures/RangedStream.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace PdfSharpCore.Pdf.Signatures
+{
+ public class RangedStream : Stream
+ {
+ private Range[] ranges;
+
+ public class Range
+ {
+
+ public Range(long offset, long length)
+ {
+ this.Offset = offset;
+ this.Length = length;
+ }
+ public long Offset { get; set; }
+ public long Length { get; set; }
+ }
+
+ private Stream stream { get; set; }
+
+
+ public RangedStream(Stream originalStrem, List ranges)
+ {
+ this.stream = originalStrem;
+
+ long previousPosition = 0;
+
+ this.ranges = ranges.OrderBy(item => item.Offset).ToArray();
+ foreach (var range in ranges)
+ {
+ if (range.Offset < previousPosition)
+ throw new Exception("Ranges are not continuous");
+ previousPosition = range.Offset + range.Length ;
+ }
+ }
+
+
+ public override bool CanRead
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override bool CanSeek
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public override bool CanWrite
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public override long Length
+ {
+ get
+ {
+ return ranges.Sum(item => item.Length);
+ }
+ }
+
+
+ private IEnumerable GetPreviousRanges(long position)
+ {
+ return ranges.Where(item => item.Offset < position && item.Offset + item.Length < position);
+ }
+
+ private Range GetCurrentRange(long position)
+ {
+ return ranges.FirstOrDefault(item => item.Offset <= position && item.Offset + item.Length > position);
+ }
+
+
+
+ public override long Position
+ {
+ get
+ {
+ return GetPreviousRanges(stream.Position).Sum(item => item.Length) + stream.Position - GetCurrentRange(stream.Position).Offset;
+ }
+
+ set
+ {
+ Range currentRange = null;
+ List previousRanges = new List();
+ long maxPosition = 0;
+ foreach (var range in ranges)
+ {
+ currentRange = range;
+ maxPosition += range.Length;
+ if (maxPosition > value)
+ break;
+ previousRanges.Add(range);
+ }
+
+ long positionInCurrentRange = value - previousRanges.Sum(item => item.Length);
+ stream.Position = currentRange.Offset + positionInCurrentRange;
+ }
+ }
+
+
+
+ public override void Flush()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+
+ var length = stream.Length;
+ int retVal = 0;
+ for (int i = 0; i < count; i++)
+ {
+
+ if (stream.Position == length)
+ {
+ break;
+ }
+
+ PerformSkipIfNeeded();
+ retVal += stream.Read(buffer, offset++, 1);
+
+ }
+
+ return retVal;
+ }
+
+
+ private void PerformSkipIfNeeded()
+ {
+ var currentRange = GetCurrentRange(stream.Position);
+
+ if (currentRange == null)
+ stream.Position = GetNextRange().Offset;
+ }
+
+ private Range GetNextRange()
+ {
+ return ranges.OrderBy(item => item.Offset).First(item => item.Offset > stream.Position);
+ }
+
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/PdfSharpCore/Pdf/PdfArray.cs b/PdfSharpCore/Pdf/PdfArray.cs
index 0b43ce0c..23d21018 100644
--- a/PdfSharpCore/Pdf/PdfArray.cs
+++ b/PdfSharpCore/Pdf/PdfArray.cs
@@ -1,4 +1,4 @@
-#region PDFsharp - A .NET library for processing PDF
+#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
@@ -70,6 +70,19 @@ public PdfArray(PdfDocument document, params PdfItem[] items)
Elements.Add(item);
}
+
+ public PdfArray(params PdfItem[] items)
+ {
+ foreach (PdfItem item in items)
+ Elements.Add(item);
+ }
+
+ public PdfArray(PdfDocument document, int paddingRight, params PdfItem[] items)
+ : this(document, items)
+ {
+ this.PaddingRight = paddingRight;
+ }
+
///
/// Initializes a new instance from an existing dictionary. Used for object type transformation.
///
@@ -118,6 +131,8 @@ public ArrayElements Elements
get { return _elements ?? (_elements = new ArrayElements(this)); }
}
+ public int PaddingRight { get; private set; }
+
///
/// Returns an enumerator that iterates through a collection.
///
@@ -152,9 +167,17 @@ internal override void WriteObject(PdfWriter writer)
for (int idx = 0; idx < count; idx++)
{
PdfItem value = Elements[idx];
- value.WriteObject(writer);
+ value.Write(writer);
}
writer.WriteEndObject();
+ if (PaddingRight > 0)
+ {
+ var bytes = new byte[PaddingRight];
+ for (int i = 0; i < PaddingRight; i++)
+ bytes[i] = 32;
+
+ writer.Write(bytes);
+ }
}
///
diff --git a/PdfSharpCore/Pdf/PdfDictionary.cs b/PdfSharpCore/Pdf/PdfDictionary.cs
index 4391e1ff..35fcc159 100644
--- a/PdfSharpCore/Pdf/PdfDictionary.cs
+++ b/PdfSharpCore/Pdf/PdfDictionary.cs
@@ -234,8 +234,8 @@ internal virtual void WriteDictionaryElement(PdfWriter writer, PdfName key)
Debug.Assert(false, "Check when we come here.");
}
#endif
- key.WriteObject(writer);
- item.WriteObject(writer);
+ key.Write(writer);
+ item.Write(writer);
writer.NewLine();
}
diff --git a/PdfSharpCore/Pdf/PdfDocument.cs b/PdfSharpCore/Pdf/PdfDocument.cs
index b7a355b8..0c3a72cd 100644
--- a/PdfSharpCore/Pdf/PdfDocument.cs
+++ b/PdfSharpCore/Pdf/PdfDocument.cs
@@ -40,12 +40,19 @@
namespace PdfSharpCore.Pdf
{
+ internal class PdfDocumentEventArgs : EventArgs
+ {
+ public PdfWriter Writer { get; set; }
+ }
+
///
/// Represents a PDF document.
///
[DebuggerDisplay("(Name={Name})")] // A name makes debugging easier
public sealed class PdfDocument : PdfObject, IDisposable
{
+ internal event EventHandler BeforeSave = (s, e) => { };
+ internal event EventHandler AfterSave = (s, e) => { };
internal DocumentState _state;
internal PdfDocumentOpenMode _openMode;
@@ -245,7 +252,7 @@ public void Save(string path)
throw new InvalidOperationException(PSSR.CannotModify);
- using (Stream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ using (Stream stream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
Save(stream);
}
@@ -304,6 +311,8 @@ public void Save(Stream stream)
///
void DoSave(PdfWriter writer)
{
+ this.BeforeSave(this, EventArgs.Empty);
+
if (_pages == null || _pages.Count == 0)
{
if (_outStream != null)
@@ -354,13 +363,13 @@ void DoSave(PdfWriter writer)
GetType();
#endif
iref.Position = writer.Position;
- iref.Value.WriteObject(writer);
+ iref.Value.Write(writer);
}
var startxref = writer.Position;
_irefTable.WriteObject(writer);
writer.WriteRaw("trailer\n");
_trailer.Elements.SetInteger("/Size", count + 1);
- _trailer.WriteObject(writer);
+ _trailer.Write(writer);
writer.WriteEof(this, startxref);
//if (encrypt)
@@ -373,6 +382,7 @@ void DoSave(PdfWriter writer)
{
if (writer != null)
{
+ this.AfterSave(this, new PdfDocumentEventArgs() { Writer = writer });
writer.Stream.Flush();
// DO NOT CLOSE WRITER HERE
//writer.Close();
diff --git a/PdfSharpCore/Pdf/PdfItem.cs b/PdfSharpCore/Pdf/PdfItem.cs
index 6abfb375..6385d6bb 100644
--- a/PdfSharpCore/Pdf/PdfItem.cs
+++ b/PdfSharpCore/Pdf/PdfItem.cs
@@ -32,12 +32,19 @@
namespace PdfSharpCore.Pdf
{
+ public class PdfItemEventArgs : EventArgs
+ {
+ public long Position { get; set; }
+ }
+
///
/// The base class of all PDF objects and simple PDF types.
///
public abstract class PdfItem : ICloneable
{
// All simple types (i.e. derived from PdfItem but not from PdfObject) must be immutable.
+ internal event EventHandler BeforeWrite;
+ internal event EventHandler AfterWrite;
object ICloneable.Clone()
{
@@ -65,5 +72,14 @@ protected virtual object Copy()
/// to the specified PdfWriter.
///
internal abstract void WriteObject(PdfWriter writer);
+
+ internal void Write(PdfWriter writer)
+ {
+ var startPosition = writer.Layout == PdfWriterLayout.Verbose ? writer.Position + 1 : writer.Position;
+
+ this.BeforeWrite?.Invoke(this, new PdfItemEventArgs() { Position = startPosition });
+ WriteObject(writer);
+ this.AfterWrite?.Invoke(this, new PdfItemEventArgs() { Position = writer.Position });
+ }
}
}
diff --git a/PdfSharpCore/Pdf/PdfString.cs b/PdfSharpCore/Pdf/PdfString.cs
index 8d2cf1b0..dfb463de 100644
--- a/PdfSharpCore/Pdf/PdfString.cs
+++ b/PdfSharpCore/Pdf/PdfString.cs
@@ -166,10 +166,11 @@ public PdfString(string value, PdfStringEncoding encoding)
_flags = (PdfStringFlags)encoding;
}
- internal PdfString(string value, PdfStringFlags flags)
+ internal PdfString(string value, PdfStringFlags flags, int paddingLeft = 0)
{
_value = value;
_flags = flags;
+ this.PaddingLeft = paddingLeft;
}
///
@@ -223,6 +224,8 @@ internal byte[] EncryptionValue
set { _value = PdfEncoders.RawEncoding.GetString(value, 0, value.Length); }
}
+ public int PaddingLeft { get; private set; }
+
///
/// Returns the string.
///
@@ -232,7 +235,7 @@ public override string ToString()
PdfStringEncoding encoding = (PdfStringEncoding)(_flags & PdfStringFlags.EncodingMask);
string pdf = (_flags & PdfStringFlags.HexLiteral) == 0 ?
PdfEncoders.ToStringLiteral(_value, encoding, null) :
- PdfEncoders.ToHexStringLiteral(_value, encoding, null);
+ PdfEncoders.ToHexStringLiteral(_value, encoding, null, PaddingLeft);
return pdf;
#else
return _value;
diff --git a/PdfSharpCore/PdfSharpCore.csproj b/PdfSharpCore/PdfSharpCore.csproj
index 4a27a4c1..81d47d4d 100644
--- a/PdfSharpCore/PdfSharpCore.csproj
+++ b/PdfSharpCore/PdfSharpCore.csproj
@@ -40,6 +40,7 @@ PdfSharpCore is a partial port of PdfSharp.Xamarin for .NET Core Additionally Mi
+