#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
}
}
}
}