#region PDFsharp - A .NET library for processing PDF // // Authors: // Stefan Lange // // Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.com // http://sourceforge.net/projects/pdfsharp // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #endregion using System; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Text; using PdfSharp.Drawing; using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Filters; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Internal; namespace PdfSharp.Pdf { /// /// Value creation flags. Specifies whether and how a value that does not exist is created. /// // ReSharper disable InconsistentNaming public enum VCF // ReSharper restore InconsistentNaming { /// /// Don't create the value. /// None, /// /// Create the value as direct object. /// Create, /// /// Create the value as indirect object. /// CreateIndirect, } /// /// Represents a PDF dictionary object. /// [DebuggerDisplay("{DebuggerDisplay}")] public class PdfDictionary : PdfObject, IEnumerable> { // Reference: 3.2.6  Dictionary Objects / Page 59 /// /// Initializes a new instance of the class. /// public PdfDictionary() { } /// /// Initializes a new instance of the class. /// /// The document. public PdfDictionary(PdfDocument document) : base(document) { } /// /// Initializes a new instance from an existing dictionary. Used for object type transformation. /// protected PdfDictionary(PdfDictionary dict) : base(dict) { if (dict._elements != null) dict._elements.ChangeOwner(this); if (dict._stream != null) dict._stream.ChangeOwner(this); } /// /// Creates a copy of this dictionary. Direct values are deep copied. Indirect references are not /// modified. /// public new PdfDictionary Clone() { return (PdfDictionary)Copy(); } /// /// This function is useful for importing objects from external documents. The returned object is not /// yet complete. irefs refer to external objects and directed objects are cloned but their document /// property is null. A cloned dictionary or array needs a 'fix-up' to be a valid object. /// protected override object Copy() { PdfDictionary dict = (PdfDictionary)base.Copy(); if (dict._elements != null) { dict._elements = dict._elements.Clone(); dict._elements.ChangeOwner(dict); PdfName[] names = dict._elements.KeyNames; foreach (PdfName name in names) { PdfObject obj = dict._elements[name] as PdfObject; if (obj != null) { obj = obj.Clone(); // Recall that obj.Document is now null. dict._elements[name] = obj; } } } if (dict._stream != null) { dict._stream = dict._stream.Clone(); dict._stream.ChangeOwner(dict); } return dict; } /// /// Gets the dictionary containing the elements of this dictionary. /// public DictionaryElements Elements { get { return _elements ?? (_elements = new DictionaryElements(this)); } } /// /// The elements of the dictionary. /// internal DictionaryElements _elements; /// /// Returns an enumerator that iterates through the dictionary elements. /// public IEnumerator> GetEnumerator() { return Elements.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Returns a string with the content of this object in a readable form. Useful for debugging purposes only. /// public override string ToString() { // Get keys and sort. PdfName[] keys = Elements.KeyNames; List list = new List(keys); list.Sort(PdfName.Comparer); list.CopyTo(keys, 0); StringBuilder pdf = new StringBuilder(); pdf.Append("<< "); foreach (PdfName key in keys) pdf.Append(key + " " + Elements[key] + " "); pdf.Append(">>"); return pdf.ToString(); } internal override void WriteObject(PdfWriter writer) { writer.WriteBeginObject(this); //int count = Elements.Count; PdfName[] keys = Elements.KeyNames; #if DEBUG // TODO: automatically set length if (_stream != null) Debug.Assert(Elements.ContainsKey(PdfStream.Keys.Length), "Dictionary has a stream but no length is set."); #endif #if DEBUG // Sort keys for debugging purposes. Comparing PDF files with for example programs like // Araxis Merge is easier with sorted keys. if (writer.Layout == PdfWriterLayout.Verbose) { List list = new List(keys); list.Sort(PdfName.Comparer); list.CopyTo(keys, 0); } #endif foreach (PdfName key in keys) WriteDictionaryElement(writer, key); if (Stream != null) WriteDictionaryStream(writer); writer.WriteEndObject(); } /// /// Writes a key/value pair of this dictionary. This function is intended to be overridden /// in derived classes. /// internal virtual void WriteDictionaryElement(PdfWriter writer, PdfName key) { if (key == null) throw new ArgumentNullException("key"); PdfItem item = Elements[key]; #if DEBUG // TODO: simplify PDFsharp if (item is PdfObject && ((PdfObject)item).IsIndirect) { // Replace an indirect object by its Reference. item = ((PdfObject)item).Reference; Debug.Assert(false, "Check when we come here."); } #endif key.WriteObject(writer); item.WriteObject(writer); writer.NewLine(); } /// /// Writes the stream of this dictionary. This function is intended to be overridden /// in a derived class. /// internal virtual void WriteDictionaryStream(PdfWriter writer) { writer.WriteStream(this, (writer.Options & PdfWriterOptions.OmitStream) == PdfWriterOptions.OmitStream); } /// /// Gets or sets the PDF stream belonging to this dictionary. Returns null if the dictionary has /// no stream. To create the stream, call the CreateStream function. /// public PdfStream Stream { get { return _stream; } set { _stream = value; } } PdfStream _stream; /// /// Creates the stream of this dictionary and initializes it with the specified byte array. /// The function must not be called if the dictionary already has a stream. /// public PdfStream CreateStream(byte[] value) { if (_stream != null) throw new InvalidOperationException("The dictionary already has a stream."); _stream = new PdfStream(value, this); // Always set the length. Elements[PdfStream.Keys.Length] = new PdfInteger(_stream.Length); return _stream; } /// /// When overridden in a derived class, gets the KeysMeta of this dictionary type. /// internal virtual DictionaryMeta Meta { get { return null; } } /// /// Represents the interface to the elements of a PDF dictionary. /// [DebuggerDisplay("{DebuggerDisplay}")] public sealed class DictionaryElements : IDictionary, ICloneable { internal DictionaryElements(PdfDictionary ownerDictionary) { _elements = new Dictionary(); _ownerDictionary = ownerDictionary; } object ICloneable.Clone() { DictionaryElements dictionaryElements = (DictionaryElements)MemberwiseClone(); dictionaryElements._elements = new Dictionary(dictionaryElements._elements); dictionaryElements._ownerDictionary = null; return dictionaryElements; } /// /// Creates a shallow copy of this object. The clone is not owned by a dictionary anymore. /// public DictionaryElements Clone() { return (DictionaryElements)((ICloneable)this).Clone(); } /// /// Moves this instance to another dictionary during object type transformation. /// internal void ChangeOwner(PdfDictionary ownerDictionary) { if (_ownerDictionary != null) { // ??? } // Set new owner. _ownerDictionary = ownerDictionary; // Set owners elements to this. ownerDictionary._elements = this; } /// /// Gets the dictionary to which this elements object belongs to. /// internal PdfDictionary Owner { get { return _ownerDictionary; } } /// /// Converts the specified value to boolean. /// If the value does not exist, the function returns false. /// If the value is not convertible, the function throws an InvalidCastException. /// public bool GetBoolean(string key, bool create) { object obj = this[key]; if (obj == null) { if (create) this[key] = new PdfBoolean(); return false; } if (obj is PdfReference) obj = ((PdfReference)obj).Value; PdfBoolean boolean = obj as PdfBoolean; if (boolean != null) return boolean.Value; PdfBooleanObject booleanObject = obj as PdfBooleanObject; if (booleanObject != null) return booleanObject.Value; throw new InvalidCastException("GetBoolean: Object is not a boolean."); } /// /// Converts the specified value to boolean. /// If the value does not exist, the function returns false. /// If the value is not convertible, the function throws an InvalidCastException. /// public bool GetBoolean(string key) { return GetBoolean(key, false); } /// /// Sets the entry to a direct boolean value. /// public void SetBoolean(string key, bool value) { this[key] = new PdfBoolean(value); } /// /// Converts the specified value to integer. /// If the value does not exist, the function returns 0. /// If the value is not convertible, the function throws an InvalidCastException. /// public int GetInteger(string key, bool create) { object obj = this[key]; if (obj == null) { if (create) this[key] = new PdfInteger(); return 0; } PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfInteger integer = obj as PdfInteger; if (integer != null) return integer.Value; PdfIntegerObject integerObject = obj as PdfIntegerObject; if (integerObject != null) return integerObject.Value; throw new InvalidCastException("GetInteger: Object is not an integer."); } /// /// Converts the specified value to integer. /// If the value does not exist, the function returns 0. /// If the value is not convertible, the function throws an InvalidCastException. /// public int GetInteger(string key) { return GetInteger(key, false); } /// /// Sets the entry to a direct integer value. /// public void SetInteger(string key, int value) { this[key] = new PdfInteger(value); } /// /// Converts the specified value to double. /// If the value does not exist, the function returns 0. /// If the value is not convertible, the function throws an InvalidCastException. /// public double GetReal(string key, bool create) { object obj = this[key]; if (obj == null) { if (create) this[key] = new PdfReal(); return 0; } PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfReal real = obj as PdfReal; if (real != null) return real.Value; PdfRealObject realObject = obj as PdfRealObject; if (realObject != null) return realObject.Value; PdfInteger integer = obj as PdfInteger; if (integer != null) return integer.Value; PdfIntegerObject integerObject = obj as PdfIntegerObject; if (integerObject != null) return integerObject.Value; throw new InvalidCastException("GetReal: Object is not a number."); } /// /// Converts the specified value to double. /// If the value does not exist, the function returns 0. /// If the value is not convertible, the function throws an InvalidCastException. /// public double GetReal(string key) { return GetReal(key, false); } /// /// Sets the entry to a direct double value. /// public void SetReal(string key, double value) { this[key] = new PdfReal(value); } /// /// Converts the specified value to String. /// If the value does not exist, the function returns the empty string. /// public string GetString(string key, bool create) { object obj = this[key]; if (obj == null) { if (create) this[key] = new PdfString(); return ""; } PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfString str = obj as PdfString; if (str != null) return str.Value; PdfStringObject strObject = obj as PdfStringObject; if (strObject != null) return strObject.Value; PdfName name = obj as PdfName; if (name != null) return name.Value; PdfNameObject nameObject = obj as PdfNameObject; if (nameObject != null) return nameObject.Value; throw new InvalidCastException("GetString: Object is not a string."); } /// /// Converts the specified value to String. /// If the value does not exist, the function returns the empty string. /// public string GetString(string key) { return GetString(key, false); } /// /// Tries to get the string. TODO: more TryGet... /// public bool TryGetString(string key, out string value) { value = null; object obj = this[key]; if (obj == null) return false; PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfString str = obj as PdfString; if (str != null) { value = str.Value; return true; } PdfStringObject strObject = obj as PdfStringObject; if (strObject != null) { value = strObject.Value; return true; } PdfName name = obj as PdfName; if (name != null) { value = name.Value; return true; } PdfNameObject nameObject = obj as PdfNameObject; if (nameObject != null) { value = nameObject.Value; return true; } return false; } /// /// Sets the entry to a direct string value. /// public void SetString(string key, string value) { this[key] = new PdfString(value); } /// /// Converts the specified value to a name. /// If the value does not exist, the function returns the empty string. /// public string GetName(string key) { object obj = this[key]; if (obj == null) { //if (create) // this[key] = new Pdf(); return String.Empty; } PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfName name = obj as PdfName; if (name != null) return name.Value; PdfNameObject nameObject = obj as PdfNameObject; if (nameObject != null) return nameObject.Value; throw new InvalidCastException("GetName: Object is not a name."); } /// /// Sets the specified name value. /// If the value doesn't start with a slash, it is added automatically. /// public void SetName(string key, string value) { if (value == null) throw new ArgumentNullException("value"); if (value.Length == 0 || value[0] != '/') value = "/" + value; this[key] = new PdfName(value); } /// /// Converts the specified value to PdfRectangle. /// If the value does not exist, the function returns an empty rectangle. /// If the value is not convertible, the function throws an InvalidCastException. /// public PdfRectangle GetRectangle(string key, bool create) { PdfRectangle value = new PdfRectangle(); object obj = this[key]; if (obj == null) { if (create) this[key] = value = new PdfRectangle(); return value; } if (obj is PdfReference) obj = ((PdfReference)obj).Value; PdfArray array = obj as PdfArray; if (array != null && array.Elements.Count == 4) { value = new PdfRectangle(array.Elements.GetReal(0), array.Elements.GetReal(1), array.Elements.GetReal(2), array.Elements.GetReal(3)); this[key] = value; } else value = (PdfRectangle)obj; return value; } /// /// Converts the specified value to PdfRectangle. /// If the value does not exist, the function returns an empty rectangle. /// If the value is not convertible, the function throws an InvalidCastException. /// public PdfRectangle GetRectangle(string key) { return GetRectangle(key, false); } /// /// Sets the entry to a direct rectangle value, represented by an array with four values. /// public void SetRectangle(string key, PdfRectangle rect) { _elements[key] = rect; } /// Converts the specified value to XMatrix. /// If the value does not exist, the function returns an identity matrix. /// If the value is not convertible, the function throws an InvalidCastException. public XMatrix GetMatrix(string key, bool create) { XMatrix value = new XMatrix(); object obj = this[key]; if (obj == null) { if (create) this[key] = new PdfLiteral("[1 0 0 1 0 0]"); // cannot be parsed, implement a PdfMatrix... return value; } PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfArray array = obj as PdfArray; if (array != null && array.Elements.Count == 6) { value = new XMatrix(array.Elements.GetReal(0), array.Elements.GetReal(1), array.Elements.GetReal(2), array.Elements.GetReal(3), array.Elements.GetReal(4), array.Elements.GetReal(5)); } else if (obj is PdfLiteral) { throw new NotImplementedException("Parsing matrix from literal."); } else throw new InvalidCastException("Element is not an array with 6 values."); return value; } /// Converts the specified value to XMatrix. /// If the value does not exist, the function returns an identity matrix. /// If the value is not convertible, the function throws an InvalidCastException. public XMatrix GetMatrix(string key) { return GetMatrix(key, false); } /// /// Sets the entry to a direct matrix value, represented by an array with six values. /// public void SetMatrix(string key, XMatrix matrix) { _elements[key] = PdfLiteral.FromMatrix(matrix); } /// /// Converts the specified value to DateTime. /// If the value does not exist, the function returns the specified default value. /// If the value is not convertible, the function throws an InvalidCastException. /// public DateTime GetDateTime(string key, DateTime defaultValue) { object obj = this[key]; if (obj == null) { return defaultValue; } PdfReference reference = obj as PdfReference; if (reference != null) obj = reference.Value; PdfDate date = obj as PdfDate; if (date != null) return date.Value; string strDate; PdfString pdfString = obj as PdfString; if (pdfString != null) strDate = pdfString.Value; else { PdfStringObject stringObject = obj as PdfStringObject; if (stringObject != null) strDate = stringObject.Value; else throw new InvalidCastException("GetName: Object is not a name."); } if (strDate != "") { try { defaultValue = Parser.ParseDateTime(strDate, defaultValue); } // ReSharper disable EmptyGeneralCatchClause catch { } // ReSharper restore EmptyGeneralCatchClause } return defaultValue; } /// /// Sets the entry to a direct datetime value. /// public void SetDateTime(string key, DateTime value) { _elements[key] = new PdfDate(value); } internal int GetEnumFromName(string key, object defaultValue, bool create) { if (!(defaultValue is Enum)) throw new ArgumentException("defaultValue"); object obj = this[key]; if (obj == null) { if (create) this[key] = new PdfName(defaultValue.ToString()); // ReSharper disable once PossibleInvalidCastException because Enum objects can always be casted to int. return (int)defaultValue; } Debug.Assert(obj is Enum); return (int)Enum.Parse(defaultValue.GetType(), obj.ToString().Substring(1), false); } internal int GetEnumFromName(string key, object defaultValue) { return GetEnumFromName(key, defaultValue, false); } internal void SetEnumAsName(string key, object value) { if (!(value is Enum)) throw new ArgumentException("value"); _elements[key] = new PdfName("/" + value); } /// /// Gets the value for the specified key. If the value does not exist, it is optionally created. /// public PdfItem GetValue(string key, VCF options) { PdfObject obj; PdfDictionary dict; PdfArray array; PdfReference iref; PdfItem value = this[key]; if (value == null || value is PdfNull || value is PdfReference && ((PdfReference)value).Value is PdfNullObject) { if (options != VCF.None) { #if NETFX_CORE && DEBUG_ if (key == "/Resources") Debug-Break.Break(); #endif Type type = GetValueType(key); if (type != null) { #if !NETFX_CORE Debug.Assert(typeof(PdfItem).IsAssignableFrom(type), "Type not allowed."); if (typeof(PdfDictionary).IsAssignableFrom(type)) { value = obj = CreateDictionary(type, null); } else if (typeof(PdfArray).IsAssignableFrom(type)) { value = obj = CreateArray(type, null); } else throw new NotImplementedException("Type other than array or dictionary."); #else // Rewritten WinRT style. TypeInfo typeInfo = type.GetTypeInfo(); Debug.Assert(typeof(PdfItem).GetTypeInfo().IsAssignableFrom(typeInfo), "Type not allowed."); if (typeof(PdfDictionary).GetTypeInfo().IsAssignableFrom(typeInfo)) { value = obj = CreateDictionary(type, null); } else if (typeof(PdfArray).GetTypeInfo().IsAssignableFrom(typeInfo)) { value = obj = CreateArray(type, null); } else throw new NotImplementedException("Type other than array or dictionary."); #endif if (options == VCF.CreateIndirect) { _ownerDictionary.Owner._irefTable.Add(obj); this[key] = obj.Reference; } else this[key] = obj; } else throw new NotImplementedException("Cannot create value for key: " + key); } } else { // The value exists and can be returned. But for imported documents check for necessary // object type transformation. if ((iref = value as PdfReference) != null) { // Case: value is an indirect reference. value = iref.Value; if (value == null) { // If we come here PDF file is corrupted. throw new InvalidOperationException("Indirect reference without value."); } if (true) // || _owner.Document.IsImported) { Type type = GetValueType(key); Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support."); #if !NETFX_CORE if (type != null && type != value.GetType()) { if (typeof(PdfDictionary).IsAssignableFrom(type)) { Debug.Assert(value is PdfDictionary, "Bug in PDFsharp. Please send this file to PDFsharp support."); value = CreateDictionary(type, (PdfDictionary)value); } else if (typeof(PdfArray).IsAssignableFrom(type)) { Debug.Assert(value is PdfArray, "Bug in PDFsharp. Please send this file to PDFsharp support."); value = CreateArray(type, (PdfArray)value); } else throw new NotImplementedException("Type other than array or dictionary."); } #else // Rewritten WinRT style. TypeInfo typeInfo = type.GetTypeInfo(); if (type != null && type != value.GetType()) { if (typeof(PdfDictionary).GetTypeInfo().IsAssignableFrom(typeInfo)) { Debug.Assert(value is PdfDictionary, "Bug in PDFsharp. Please send this file to PDFsharp support."); value = CreateDictionary(type, (PdfDictionary)value); } else if (typeof(PdfArray).GetTypeInfo().IsAssignableFrom(typeInfo)) { Debug.Assert(value is PdfArray, "Bug in PDFsharp. Please send this file to PDFsharp support."); value = CreateArray(type, (PdfArray)value); } else throw new NotImplementedException("Type other than array or dictionary."); } #endif } return value; } // Transformation is only possible after PDF import. if (true) // || _owner.Document.IsImported) { // Case: value is a direct object if ((dict = value as PdfDictionary) != null) { Debug.Assert(!dict.IsIndirect); Type type = GetValueType(key); Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support."); if (dict.GetType() != type) dict = CreateDictionary(type, dict); return dict; } if ((array = value as PdfArray) != null) { Debug.Assert(!array.IsIndirect); Type type = GetValueType(key); // This is more complicated. If type is null do nothing //Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support."); if (type != null && type != array.GetType()) array = CreateArray(type, array); return array; } } } return value; } /// /// Short cut for GetValue(key, VCF.None). /// public PdfItem GetValue(string key) { return GetValue(key, VCF.None); } /// /// Returns the type of the object to be created as value of the specified key. /// Type GetValueType(string key) // TODO: move to PdfObject { Type type = null; DictionaryMeta meta = _ownerDictionary.Meta; if (meta != null) { KeyDescriptor kd = meta[key]; if (kd != null) type = kd.GetValueType(); //else // Debug.WriteLine("Warning: Key not descriptor table: " + key); // TODO: check what this means... } //else // Debug.WriteLine("Warning: No meta provided for type: " + _owner.GetType().Name); // TODO: check what this means... return type; } PdfArray CreateArray(Type type, PdfArray oldArray) { #if !NETFX_CORE && !UWP ConstructorInfo ctorInfo; PdfArray array; if (oldArray == null) { // Use constructor with signature 'Ctor(PdfDocument owner)'. ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDocument) }, null); Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); array = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfArray; } else { // Use constructor with signature 'Ctor(PdfDictionary dict)'. ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfArray) }, null); Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray; } return array; #else // Rewritten WinRT style. PdfArray array = null; if (oldArray == null) { // Use constructor with signature 'Ctor(PdfDocument owner)'. var ctorInfos = type.GetTypeInfo().DeclaredConstructors; //.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, //null, new Type[] { typeof(PdfDocument) }, null); foreach (var ctorInfo in ctorInfos) { var parameters = ctorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument)) { array = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfArray; break; } } Debug.Assert(array != null, "No appropriate constructor found for type: " + type.Name); } else { // Use constructor with signature 'Ctor(PdfDictionary dict)'. var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // .GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, //null, new Type[] { typeof(PdfArray) }, null); foreach (var ctorInfo in ctorInfos) { var parameters = ctorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfArray)) { array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray; break; } } Debug.Assert(array != null, "No appropriate constructor found for type: " + type.Name); } return array; #endif } PdfDictionary CreateDictionary(Type type, PdfDictionary oldDictionary) { #if !NETFX_CORE && !UWP ConstructorInfo ctorInfo; PdfDictionary dict; if (oldDictionary == null) { // Use constructor with signature 'Ctor(PdfDocument owner)'. ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDocument) }, null); Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary; } else { // Use constructor with signature 'Ctor(PdfDictionary dict)'. ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDictionary) }, null); Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); dict = ctorInfo.Invoke(new object[] { oldDictionary }) as PdfDictionary; } return dict; #else // Rewritten WinRT style. PdfDictionary dict = null; if (oldDictionary == null) { // Use constructor with signature 'Ctor(PdfDocument owner)'. var ctorInfos = type.GetTypeInfo().DeclaredConstructors; //GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, //null, new Type[] { typeof(PdfDocument) }, null); foreach (var ctorInfo in ctorInfos) { var parameters = ctorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument)) { dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary; break; } } Debug.Assert(dict != null, "No appropriate constructor found for type: " + type.Name); } else { var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDictionary) }, null); foreach (var ctorInfo in ctorInfos) { var parameters = ctorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDictionary)) { dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary; break; } } Debug.Assert(dict != null, "No appropriate constructor found for type: " + type.Name); } return dict; #endif } PdfItem CreateValue(Type type, PdfDictionary oldValue) { #if !NETFX_CORE && !UWP ConstructorInfo ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDocument) }, null); PdfObject obj = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfObject; if (oldValue != null) { obj.Reference = oldValue.Reference; obj.Reference.Value = obj; if (obj is PdfDictionary) { PdfDictionary dict = (PdfDictionary)obj; dict._elements = oldValue._elements; } } return obj; #else // Rewritten WinRT style. PdfObject obj = null; var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDocument) }, null); foreach (var ctorInfo in ctorInfos) { var parameters = ctorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument)) { obj = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfObject; break; } } Debug.Assert(obj != null, "No appropriate constructor found for type: " + type.Name); if (oldValue != null) { obj.Reference = oldValue.Reference; obj.Reference.Value = obj; if (obj is PdfDictionary) { PdfDictionary dict = (PdfDictionary)obj; dict._elements = oldValue._elements; } } return obj; #endif } /// /// Sets the entry with the specified value. DON'T USE THIS FUNCTION - IT MAY BE REMOVED. /// public void SetValue(string key, PdfItem value) { Debug.Assert((value is PdfObject && ((PdfObject)value).Reference == null) | !(value is PdfObject), "You try to set an indirect object directly into a dictionary."); // HACK? _elements[key] = value; } ///// ///// Returns the indirect object if the value of the specified key is a PdfReference. ///// //[Obsolete("Use GetObject, GetDictionary, GetArray, or GetReference")] //public PdfObject GetIndirectObject(string key) //{ // PdfItem item = this[key]; // if (item is PdfReference) // return ((PdfReference)item).Value; // return null; //} /// /// Gets the PdfObject with the specified key, or null, if no such object exists. If the key refers to /// a reference, the referenced PdfObject is returned. /// public PdfObject GetObject(string key) { PdfItem item = this[key]; PdfReference reference = item as PdfReference; if (reference != null) return reference.Value; return item as PdfObject; } /// /// Gets the PdfDictionary with the specified key, or null, if no such object exists. If the key refers to /// a reference, the referenced PdfDictionary is returned. /// public PdfDictionary GetDictionary(string key) { return GetObject(key) as PdfDictionary; } /// /// Gets the PdfArray with the specified key, or null, if no such object exists. If the key refers to /// a reference, the referenced PdfArray is returned. /// public PdfArray GetArray(string key) { return GetObject(key) as PdfArray; } /// /// Gets the PdfReference with the specified key, or null, if no such object exists. /// public PdfReference GetReference(string key) { PdfItem item = this[key]; return item as PdfReference; } /// /// Sets the entry to the specified object. The object must not be an indirect object, /// otherwise an exception is raised. /// public void SetObject(string key, PdfObject obj) { if (obj.Reference != null) throw new ArgumentException("PdfObject must not be an indirect object.", "obj"); this[key] = obj; } /// /// Sets the entry as a reference to the specified object. The object must be an indirect object, /// otherwise an exception is raised. /// public void SetReference(string key, PdfObject obj) { if (obj.Reference == null) throw new ArgumentException("PdfObject must be an indirect object.", "obj"); this[key] = obj.Reference; } /// /// Sets the entry as a reference to the specified iref. /// public void SetReference(string key, PdfReference iref) { if (iref == null) throw new ArgumentNullException("iref"); this[key] = iref; } #region IDictionary Members /// /// Gets a value indicating whether the object is read-only. /// public bool IsReadOnly { get { return false; } } /// /// Returns an object for the object. /// public IEnumerator> GetEnumerator() { return _elements.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((ICollection)_elements).GetEnumerator(); } /// /// Gets or sets an entry in the dictionary. The specified key must be a valid PDF name /// starting with a slash '/'. This property provides full access to the elements of the /// PDF dictionary. Wrong use can lead to errors or corrupt PDF files. /// public PdfItem this[string key] { get { PdfItem item; _elements.TryGetValue(key, out item); return item; } set { if (value == null) throw new ArgumentNullException("value"); #if DEBUG_ if (key == "/MediaBox") key.GetType(); //if (value is PdfObject) //{ // PdfObject obj = (PdfObject)value; // if (obj.Reference != null) // throw new ArgumentException("An object with an indirect reference cannot be a direct value. Try to set an indirect reference."); //} if (value is PdfDictionary) { PdfDictionary dict = (PdfDictionary)value; if (dict._stream != null) throw new ArgumentException("A dictionary with stream cannot be a direct value."); } #endif PdfObject obj = value as PdfObject; if (obj != null && obj.IsIndirect) value = obj.Reference; _elements[key] = value; } } /// /// Gets or sets an entry in the dictionary identified by a PdfName object. /// public PdfItem this[PdfName key] { get { return this[key.Value]; } set { if (value == null) throw new ArgumentNullException("value"); #if DEBUG PdfDictionary dictionary = value as PdfDictionary; if (dictionary != null) { PdfDictionary dict = dictionary; if (dict._stream != null) throw new ArgumentException("A dictionary with stream cannot be a direct value."); } #endif PdfObject obj = value as PdfObject; if (obj != null && obj.IsIndirect) value = obj.Reference; _elements[key.Value] = value; } } /// /// Removes the value with the specified key. /// public bool Remove(string key) { return _elements.Remove(key); } /// /// Removes the value with the specified key. /// public bool Remove(KeyValuePair item) { throw new NotImplementedException(); } ///// ///// Determines whether the dictionary contains the specified name. ///// //[Obsolete("Use ContainsKey.")] //public bool Contains(string key) //{ // return _elements.ContainsKey(key); //} /// /// Determines whether the dictionary contains the specified name. /// public bool ContainsKey(string key) { return _elements.ContainsKey(key); } /// /// Determines whether the dictionary contains a specific value. /// public bool Contains(KeyValuePair item) { throw new NotImplementedException(); } /// /// Removes all elements from the dictionary. /// public void Clear() { _elements.Clear(); } /// /// Adds the specified value to the dictionary. /// public void Add(string key, PdfItem value) { if (String.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); if (key[0] != '/') throw new ArgumentException("The key must start with a slash '/'."); // If object is indirect automatically convert value to reference. PdfObject obj = value as PdfObject; if (obj != null && obj.IsIndirect) value = obj.Reference; _elements.Add(key, value); } /// /// Adds an item to the dictionary. /// public void Add(KeyValuePair item) { Add(item.Key, item.Value); } /// /// Gets all keys currently in use in this dictionary as an array of PdfName objects. /// public PdfName[] KeyNames { get { ICollection values = _elements.Keys; int count = values.Count; string[] strings = new string[count]; values.CopyTo(strings, 0); PdfName[] names = new PdfName[count]; for (int idx = 0; idx < count; idx++) names[idx] = new PdfName(strings[idx]); return names; } } /// /// Get all keys currently in use in this dictionary as an array of string objects. /// public ICollection Keys { // It is by design not to return _elements.Keys, but a copy. get { ICollection values = _elements.Keys; int count = values.Count; string[] keys = new string[count]; values.CopyTo(keys, 0); return keys; } } /// /// Gets the value associated with the specified key. /// public bool TryGetValue(string key, out PdfItem value) { return _elements.TryGetValue(key, out value); } /// /// Gets all values currently in use in this dictionary as an array of PdfItem objects. /// //public ICollection Values public ICollection Values { // It is by design not to return _elements.Values, but a copy. get { ICollection values = _elements.Values; PdfItem[] items = new PdfItem[values.Count]; values.CopyTo(items, 0); return items; } } /// /// Return false. /// public bool IsFixedSize { get { return false; } } #endregion #region ICollection Members /// /// Return false. /// public bool IsSynchronized { get { return false; } } /// /// Gets the number of elements contained in the dictionary. /// public int Count { get { return _elements.Count; } } /// /// Copies the elements of the dictionary to an array, starting at a particular index. /// public void CopyTo(KeyValuePair[] array, int arrayIndex) { throw new NotImplementedException(); } /// /// The current implementation returns null. /// public object SyncRoot { get { return null; } } #endregion /// /// Gets the DebuggerDisplayAttribute text. /// // ReSharper disable UnusedMember.Local internal string DebuggerDisplay // ReSharper restore UnusedMember.Local { get { StringBuilder sb = new StringBuilder(); sb.AppendFormat(CultureInfo.InvariantCulture, "key={0}:(", _elements.Count); bool addSpace = false; ICollection keys = _elements.Keys; foreach (string key in keys) { if (addSpace) sb.Append(' '); addSpace = true; sb.Append(key); } sb.Append(")"); return sb.ToString(); } } /// /// The elements of the dictionary with a string as key. /// Because the string is a name it starts always with a '/'. /// Dictionary _elements; /// /// The dictionary this objects belongs to. /// PdfDictionary _ownerDictionary; } /// /// The PDF stream objects. /// public sealed class PdfStream { internal PdfStream(PdfDictionary ownerDictionary) { if (ownerDictionary == null) throw new ArgumentNullException("ownerDictionary"); _ownerDictionary = ownerDictionary; } /// /// A .NET string can contain char(0) as a valid character. /// internal PdfStream(byte[] value, PdfDictionary owner) : this(owner) { _value = value; } /// /// Clones this stream by creating a deep copy. /// public PdfStream Clone() { PdfStream stream = (PdfStream)MemberwiseClone(); stream._ownerDictionary = null; if (stream._value != null) { stream._value = new byte[stream._value.Length]; _value.CopyTo(stream._value, 0); } return stream; } /// /// Moves this instance to another dictionary during object type transformation. /// internal void ChangeOwner(PdfDictionary dict) { if (_ownerDictionary != null) { // ??? } // Set new owner. _ownerDictionary = dict; // Set owners stream to this. _ownerDictionary._stream = this; } /// /// The dictionary the stream belongs to. /// PdfDictionary _ownerDictionary; /// /// Gets the length of the stream, i.e. the actual number of bytes in the stream. /// public int Length { get { return _value != null ? _value.Length : 0; } } /// /// Gets a value indicating whether this stream has decode parameters. /// internal bool HasDecodeParams { // TODO: Move to Stream.Internals get { // TODO: DecodeParams can be an array. PdfDictionary dictionary = _ownerDictionary.Elements.GetDictionary(Keys.DecodeParms); if (dictionary != null) { // More to do here? return true; } return false; } } /// /// Gets the decode predictor for LZW- or FlateDecode. /// Returns 0 if no such value exists. /// internal int DecodePredictor // Reference: TABLE 3.8 Predictor values / Page 76 { get { PdfDictionary dictionary = _ownerDictionary.Elements.GetDictionary(Keys.DecodeParms); if (dictionary != null) { return dictionary.Elements.GetInteger("/Predictor"); } return 0; } } /// /// Gets the decode Columns for LZW- or FlateDecode. /// Returns 0 if no such value exists. /// internal int DecodeColumns // Reference: TABLE 3.8 Predictor values / Page 76 { get { PdfDictionary dictionary = _ownerDictionary.Elements.GetDictionary(Keys.DecodeParms); if (dictionary != null) { return dictionary.Elements.GetInteger("/Columns"); } return 0; } } /// /// Get or sets the bytes of the stream as they are, i.e. if one or more filters exist the bytes are /// not unfiltered. /// public byte[] Value { get { return _value; } set { if (value == null) throw new ArgumentNullException("value"); _value = value; _ownerDictionary.Elements.SetInteger(Keys.Length, value.Length); } } byte[] _value; /// /// Gets the value of the stream unfiltered. The stream content is not modified by this operation. /// public byte[] UnfilteredValue { get { byte[] bytes = null; if (_value != null) { PdfItem filter = _ownerDictionary.Elements["/Filter"]; if (filter != null) { bytes = Filtering.Decode(_value, filter); if (bytes == null) { string message = String.Format("«Cannot decode filter '{0}'»", filter); bytes = PdfEncoders.RawEncoding.GetBytes(message); } } else { bytes = new byte[_value.Length]; _value.CopyTo(bytes, 0); } } return bytes ?? new byte[0]; } } /// /// Tries to unfilter the bytes of the stream. If the stream is filtered and PDFsharp knows the filter /// algorithm, the stream content is replaced by its unfiltered value and the function returns true. /// Otherwise the content remains untouched and the function returns false. /// The function is useful for analyzing existing PDF files. /// public bool TryUnfilter() // TODO: Take DecodeParams into account. { if (_value != null) { PdfItem filter = _ownerDictionary.Elements["/Filter"]; if (filter != null) { // PDFsharp can only uncompress streams that are compressed with the ZIP or LZH algorithm. byte[] bytes = Filtering.Decode(_value, filter); if (bytes != null) { _ownerDictionary.Elements.Remove(Keys.Filter); Value = bytes; } else return false; } } return true; } /// /// Compresses the stream with the FlateDecode filter. /// If a filter is already defined, the function has no effect. /// public void Zip() { if (_value == null) return; if (!_ownerDictionary.Elements.ContainsKey("/Filter")) { _value = Filtering.FlateDecode.Encode(_value, _ownerDictionary._document.Options.FlateEncodeMode); _ownerDictionary.Elements["/Filter"] = new PdfName("/FlateDecode"); _ownerDictionary.Elements["/Length"] = new PdfInteger(_value.Length); } } /// /// Returns the stream content as a raw string. /// public override string ToString() { if (_value == null) return "«null»"; string stream; PdfItem filter = _ownerDictionary.Elements["/Filter"]; if (filter != null) { #if true byte[] bytes = Filtering.Decode(_value, filter); if (bytes != null) stream = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length); #else if (_owner.Elements.GetString("/Filter") == "/FlateDecode") { stream = Filtering.FlateDecode.DecodeToString(_value); } #endif else throw new NotImplementedException("Unknown filter"); } else stream = PdfEncoders.RawEncoding.GetString(_value, 0, _value.Length); return stream; } //internal void WriteObject_(Stream stream) //{ // if (_value != null) // stream.Write(_value, 0, value.Length); //} ///// ///// Converts a raw encoded string into a byte array. ///// //public static byte[] RawEncode(string content) //{ // return PdfEncoders.RawEncoding.GetBytes(content); //} /// /// Common keys for all streams. /// public class Keys : KeysBase { // ReSharper disable InconsistentNaming /// /// (Required) The number of bytes from the beginning of the line following the keyword /// stream to the last byte just before the keyword endstream. (There may be an additional /// EOL marker, preceding endstream, that is not included in the count and is not logically /// part of the stream data.) /// [KeyInfo(KeyType.Integer | KeyType.Required)] public const string Length = "/Length"; /// /// (Optional) The name of a filter to be applied in processing the stream data found between /// the keywords stream and endstream, or an array of such names. Multiple filters should be /// specified in the order in which they are to be applied. /// [KeyInfo(KeyType.NameOrArray | KeyType.Optional)] public const string Filter = "/Filter"; /// /// (Optional) A parameter dictionary or an array of such dictionaries, used by the filters /// specified by Filter. If there is only one filter and that filter has parameters, DecodeParms /// must be set to the filter’s parameter dictionary unless all the filter’s parameters have /// their default values, in which case the DecodeParms entry may be omitted. If there are /// multiple filters and any of the filters has parameters set to nondefault values, DecodeParms /// must be an array with one entry for each filter: either the parameter dictionary for that /// filter, or the null object if that filter has no parameters (or if all of its parameters have /// their default values). If none of the filters have parameters, or if all their parameters /// have default values, the DecodeParms entry may be omitted. /// [KeyInfo(KeyType.ArrayOrDictionary | KeyType.Optional)] public const string DecodeParms = "/DecodeParms"; /// /// (Optional; PDF 1.2) The file containing the stream data. If this entry is present, the bytes /// between stream and endstream are ignored, the filters are specified by FFilter rather than /// Filter, and the filter parameters are specified by FDecodeParms rather than DecodeParms. /// However, the Length entry should still specify the number of those bytes. (Usually, there are /// no bytes and Length is 0.) /// [KeyInfo("1.2", KeyType.String | KeyType.Optional)] public const string F = "/F"; /// /// (Optional; PDF 1.2) The name of a filter to be applied in processing the data found in the /// stream’s external file, or an array of such names. The same rules apply as for Filter. /// [KeyInfo("1.2", KeyType.NameOrArray | KeyType.Optional)] public const string FFilter = "/FFilter"; /// /// (Optional; PDF 1.2) A parameter dictionary, or an array of such dictionaries, used by the /// filters specified by FFilter. The same rules apply as for DecodeParms. /// [KeyInfo("1.2", KeyType.ArrayOrDictionary | KeyType.Optional)] public const string FDecodeParms = "/FDecodeParms"; /// /// Optional; PDF 1.5) A non-negative integer representing the number of bytes in the decoded /// (defiltered) stream. It can be used to determine, for example, whether enough disk space is /// available to write a stream to a file. /// This value should be considered a hint only; for some stream filters, it may not be possible /// to determine this value precisely. /// [KeyInfo("1.5", KeyType.Integer | KeyType.Optional)] public const string DL = "/DL"; // ReSharper restore InconsistentNaming } } /// /// Gets the DebuggerDisplayAttribute text. /// // ReSharper disable UnusedMember.Local string DebuggerDisplay // ReSharper restore UnusedMember.Local { get { #if true return String.Format(CultureInfo.InvariantCulture, "dictionary({0},[{1}])={2}", ObjectID.DebuggerDisplay, Elements.Count, _elements.DebuggerDisplay); #else return String.Format(CultureInfo.InvariantCulture, "dictionary({0},[{1}])=", ObjectID.DebuggerDisplay, _elements.DebuggerDisplay); #endif } } } }