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 +