|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//#define OLD_ISF
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Ink;
using System.Windows.Media;
namespace MS.Internal.Ink.InkSerializedFormat
{
/// <summary>
/// ExtendedProperty converter for ISF and serialization purposes
/// </summary>
internal static class ExtendedPropertySerializer
{
// If the ExtendedProperty identifier matches one of the original ISF/Tablet-internal
// Guids that did not include embedded type information (e.g. used the OS-internal
// property storage API), then it is always stored as byte array and does not
// include type information
private static bool UsesEmbeddedTypeInformation(Guid propGuid)
{
for (int i = 0; i < KnownIdCache.OriginalISFIdTable.Length; i++)
{
if (propGuid.Equals(KnownIdCache.OriginalISFIdTable[i]))
{
return false;
}
}
for (int i = 0; i < KnownIdCache.TabletInternalIdTable.Length; i++)
{
if (propGuid.Equals(KnownIdCache.TabletInternalIdTable[i]))
{
return false;
}
}
return true;
}
internal static void EncodeToStream(ExtendedProperty attribute, Stream stream)
{
VarEnum interopTypeInfo;
object data = attribute.Value;
// we need to find better way to discover which properties should be persistable
if (attribute.Id == KnownIds.DrawingFlags)
{
interopTypeInfo = VarEnum.VT_I4;
}
else if (attribute.Id == KnownIds.StylusTip)
{
interopTypeInfo = VarEnum.VT_I4;
}
else
{
// Find the type of the object if it's embedded in the stream
if (UsesEmbeddedTypeInformation(attribute.Id))
{
interopTypeInfo = SerializationHelper.ConvertToVarEnum(attribute.Value.GetType(), true);
}
else // Otherwise treat this as byte array
{
interopTypeInfo = (VarEnum.VT_ARRAY | VarEnum.VT_UI1);
}
}
EncodeAttribute(attribute.Id, data, interopTypeInfo, stream);
}
/// <summary>
/// This function returns the Data bytes that accurately describes the object
/// </summary>
/// <returns></returns>
internal static void EncodeAttribute(Guid guid, object value, VarEnum type, Stream stream)
{
// Presharp gives a warning when local IDisposable variables are not closed
// in this case, we can't call Dispose since it will also close the underlying stream
// which still needs to be written to
#pragma warning disable 1634, 1691
#pragma warning disable 6518
BinaryWriter bw = new BinaryWriter(stream);
// if this guid used the legacy internal attribute persistence APIs,
// then it doesn't include embedded type information (it's always a byte array)
if (UsesEmbeddedTypeInformation(guid))
{
// StylusTip is being serialized as a ushort, is this ok?
ushort datatype = (ushort)type;
bw.Write(datatype);
}
// We know the type of the object. We must serialize it accordingly.
switch(type)
{
case (VarEnum.VT_ARRAY | VarEnum.VT_I1)://8208
{
char[] data = (char[])value;
bw.Write(data);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI1)://8209
{
byte[] data = (byte[])value;
bw.Write(data);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_I2)://8194
{
short [] data = (short[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI2)://8210
{
ushort [] data = (ushort[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_I4)://8195
{
int [] data = (int[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI4)://8211
{
uint [] data = (uint[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_I8)://8212
{
long [] data = (long[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI8)://8213
{
ulong [] data = (ulong[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_R4 )://8196
{
float [] data = (float[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_R8)://8197
{
double [] data = (double[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_DATE)://8199
{
DateTime [] data = (DateTime[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i].ToOADate());
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_BOOL )://8203
{
bool [] data = (bool[])value;
for (int i = 0; i < data.Length; i++)
{
if (data[i])
{
//true is two consecutive all bits on bytes
bw.Write((byte)0xFF);
bw.Write((byte)0xFF);
}
else
{
//false is two consecutive all bits off
bw.Write((byte)0);
bw.Write((byte)0);
}
}
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_DECIMAL)://8206
{
decimal [] data = (decimal[])value;
for( int i = 0; i < data.Length; i++ )
bw.Write(data[i]);
break;
}
case (VarEnum.VT_I1)://16
{
char data = (char)value;
bw.Write(data);
break;
}
case (VarEnum.VT_UI1)://17
{
byte data = (byte)value;
bw.Write(data);
break;
}
case (VarEnum.VT_I2)://2
{
short data = (short)value;
bw.Write(data);
break;
}
case (VarEnum.VT_UI2)://18
{
ushort data = (ushort)value;
bw.Write(data);
break;
}
case (VarEnum.VT_I4)://3
{
int data = (int)value;
bw.Write(data);
break;
}
case (VarEnum.VT_UI4)://19
{
uint data = (uint)value;
bw.Write(data);
break;
}
case (VarEnum.VT_I8)://20
{
long data = (long)value;
bw.Write(data);
break;
}
case (VarEnum.VT_UI8)://21
{
ulong data = (ulong)value;
bw.Write(data);
break;
}
case (VarEnum.VT_R4 )://4
{
float data = (float)value;
bw.Write(data);
break;
}
case (VarEnum.VT_R8)://5
{
double data = (double)value;
bw.Write(data);
break;
}
case (VarEnum.VT_DATE)://7
{
DateTime data = (DateTime)value;
bw.Write(data.ToOADate());
break;
}
case (VarEnum.VT_BOOL )://11
{
bool data = (bool)value;
if (data)
{
//true is two consecutive all bits on bytes
bw.Write((byte)0xFF);
bw.Write((byte)0xFF);
}
else
{
//false is two consecutive all bits off bytes
bw.Write((byte)0);
bw.Write((byte)0);
}
break;
}
case (VarEnum.VT_DECIMAL)://14
{
decimal data = (decimal)value;
bw.Write(data);
break;
}
case (VarEnum.VT_BSTR)://8
{
string data = (string)value;
bw.Write( System.Text.Encoding.Unicode.GetBytes( data ) );
break;
}
default:
{
throw new InvalidOperationException(SR.InvalidEpInIsf);
}
}
#pragma warning restore 6518
#pragma warning restore 1634, 1691
}
#if OLD_ISF
/// <summary>
/// Encodes a custom attribute to the ISF stream
/// </summary>
#else
/// <summary>
/// Encodes a custom attribute to the ISF stream
/// </summary>
#endif
internal static uint EncodeAsISF(Guid id, byte[] data, Stream strm, GuidList guidList, byte compressionAlgorithm, bool fTag)
{
uint cbWrite = 0;
uint cbSize = GuidList.GetDataSizeIfKnownGuid(id);
Debug.Assert(strm != null);
if (fTag)
{
uint uTag = (uint)guidList.FindTag(id, true);
cbWrite += SerializationHelper.Encode(strm, uTag);
}
// If cbSize is 0, it is either a custom property or a known property with 0
// size. In either case, we need to write the size of the individual object
if (0 == cbSize)
{
// Now we need to write the actual data for the property
cbSize = (uint)data.Length;
byte[] compresseddata = Compressor.CompressPropertyData(data, compressionAlgorithm);
#if OLD_ISF
byte nAlgo = compressionAlgorithm;
uint cbOut = 0;
Compressor.CompressPropertyData(data, ref nAlgo, ref cbOut, null);
// Allocate a buffer big enough to hold the compressed data
byte[] compresseddata2 = new byte[cbOut];
// NativeCompressor the data
Compressor.CompressPropertyData(data, ref nAlgo, ref cbOut, compresseddata2);
if (compresseddata.Length != compresseddata2.Length)
{
throw new InvalidOperationException("MAGIC EXCEPTION: Property bytes length when compressed didn't match with new compression");
}
for (int i = 0; i < compresseddata.Length; i++)
{
if (compresseddata[i] != compresseddata2[i])
{
throw new InvalidOperationException("MAGIC EXCEPTION: Property data didn't match with new property compression at index " + i.ToString());
}
}
#endif
// write the encoded compressed size minus the algo byte
cbWrite += SerializationHelper.Encode(strm, (uint)(compresseddata.Length - 1));
// Write the raw data
strm.Write(compresseddata, 0, (int)compresseddata.Length);
cbWrite += (uint)compresseddata.Length;
}
else
{
//
// note that we used to write the nocompression byte, but that
// was incorrect. We must not write it because loaders do not
// expect it for known guids
//
// write the algo byte
//strm.WriteByte(Compressor.NoCompression);
//cbWrite++;
// write the raw data without compression
strm.Write(data, 0, (int)data.Length);
cbWrite += (uint)data.Length;
}
return cbWrite;
}
#if OLD_ISF
/// <summary>
/// Loads a single ExtendedProperty from the stream and add that to the list. Tag may be passed as in
/// the case of Stroke ExtendedPropertyCollection where tag is stored in the stroke descriptor or 0 when tag
/// is embeded in the stream
/// </summary>
/// <param name="stream">Memory buffer to load from</param>
/// <param name="cbSize">Maximum length of buffer to read</param>
/// <param name="guidList">Guid cache to read from</param>
/// <param name="tag">Guid tag to lookup</param>
/// <param name="guid">Guid of property</param>
/// <param name="data">Data of property</param>
/// <returns>Length of buffer read</returns>
#else
/// <summary>
/// Loads a single ExtendedProperty from the stream and add that to the list. Tag may be passed as in
/// the case of Stroke ExtendedPropertyCollection where tag is stored in the stroke descriptor or 0 when tag
/// is embeded in the stream
/// </summary>
/// <param name="stream">Memory buffer to load from</param>
/// <param name="cbSize">Maximum length of buffer to read</param>
/// <param name="guidList">Guid cache to read from</param>
/// <param name="tag">Guid tag to lookup</param>
/// <param name="guid">Guid of property</param>
/// <param name="data">Data of property</param>
/// <returns>Length of buffer read</returns>
#endif
internal static uint DecodeAsISF(Stream stream, uint cbSize, GuidList guidList, KnownTagCache.KnownTagIndex tag, ref Guid guid, out object data)
{
uint cb, cbRead = 0;
uint cbTotal = cbSize;
if (0 == cbSize)
{
throw new InvalidOperationException(SR.EmptyDataToLoad);
}
if (0 == tag) // no tag is passed, it must be embedded in the data
{
uint uiTag;
cb = SerializationHelper.Decode(stream, out uiTag);
tag = (KnownTagCache.KnownTagIndex)uiTag;
if (cb > cbTotal)
throw new ArgumentException(SR.InvalidSizeSpecified, "cbSize");
cbTotal -= cb;
cbRead += cb;
System.Diagnostics.Debug.Assert(guid == Guid.Empty);
guid = guidList.FindGuid(tag);
}
if (guid == Guid.Empty)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Custom Attribute tag embedded in ISF stream does not match guid table"), "tag");
}
// Try and find the size
uint size = GuidList.GetDataSizeIfKnownGuid(guid);
if (size > cbTotal)
throw new ArgumentException(SR.InvalidSizeSpecified, "cbSize");
// if the size is 0
if (0 == size)
{
// Size must be embedded in the stream. Find out the compressed data size
cb = SerializationHelper.Decode(stream, out size);
uint cbInsize = size + 1;
cbRead += cb;
cbTotal -= cb;
if (cbInsize > cbTotal)
throw new ArgumentException();
byte[] bytes = new byte[cbInsize];
uint bytesRead = (uint) stream.Read(bytes, 0, (int)cbInsize);
if (cbInsize != bytesRead)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Read different size from stream then expected"), "cbSize");
}
cbRead += cbInsize;
cbTotal -= cbInsize;
//Find out the Decompressed buffer size
using (MemoryStream decompressedStream = new MemoryStream(Compressor.DecompressPropertyData(bytes)))
{
// Add the property
data = ExtendedPropertySerializer.DecodeAttribute(guid, decompressedStream);
}
}
else
{
// For known size data, we just read the data directly from the stream
byte[] bytes = new byte[size];
uint bytesRead = (uint) stream.Read(bytes, 0, (int)size);
if (size != bytesRead)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Read different size from stream then expected"), "cbSize");
}
using (MemoryStream subStream = new MemoryStream(bytes))
{
data = ExtendedPropertySerializer.DecodeAttribute(guid, subStream);
}
cbTotal -= size;
cbRead += size;
}
return cbRead;
}
/// <summary>
/// Decodes a byte array (stored in the memory stream) into an object
/// If the GUID is one of the internal versions, then the type is assumed
/// to be byte array.
/// If however, the guid is of unknown origin or not v1 internal, then the type
/// information is assumed to be stored in the first 2 bytes of the stream.
/// </summary>
/// <param name="guid">Guid of property - to detect origin</param>
/// <param name="stream">Buffer of data</param>
/// <returns>object stored in data buffer</returns>
internal static object DecodeAttribute(Guid guid, Stream stream)
{
VarEnum type;
return DecodeAttribute(guid, stream, out type);
}
/// <summary>
/// Decodes a byte array (stored in the memory stream) into an object
/// If the GUID is one of the internal versions, then the type is assumed
/// to be byte array.
/// If however, the guid is of unknown origin or not v1 internal, then the type
/// information is assumed to be stored in the first 2 bytes of the stream.
/// </summary>
/// <param name="guid">Guid of property - to detect origin</param>
/// <param name="memStream">Buffer of data</param>
/// <param name="type">the type info stored in the stream</param>
/// <returns>object stored in data buffer</returns>
/// <remarks>The buffer stream passed in to the method will be closed after reading</remarks>
internal static object DecodeAttribute(Guid guid, Stream memStream, out VarEnum type)
{
// First determine the object type
using (BinaryReader br = new BinaryReader(memStream))
{
//
// if usesEmbeddedTypeInfo is true, we do not
// read the variant type from the ISF stream. Instead,
// we assume it to be a byte[]
//
bool usesEmbeddedTypeInfo = UsesEmbeddedTypeInformation(guid);
// if the Id has embedded type information then retrieve it from the stream
if (usesEmbeddedTypeInfo)
{
// We must read the data type from the stream
type = (VarEnum)br.ReadUInt16();
}
else
{
// The data is stored as byte array
type = (VarEnum.VT_ARRAY | VarEnum.VT_UI1);
}
switch (type)
{
case (VarEnum.VT_ARRAY | VarEnum.VT_I1):
return br.ReadChars((int)(memStream.Length - 2));
case (VarEnum.VT_ARRAY | VarEnum.VT_UI1):
{
//
// note: for (VarEnum.VT_ARRAY | VarEnum.VT_UI1),
// we might be reading data that didn't have the
// type embedded in the ISF stream, in which case
// we must not assume we've already read two bytes
//
int previouslyReadBytes = 2;
if (!usesEmbeddedTypeInfo)
{
previouslyReadBytes = 0;
}
return br.ReadBytes((int)(memStream.Length - previouslyReadBytes));
}
case (VarEnum.VT_ARRAY | VarEnum.VT_I2):
{
int count = (int)(memStream.Length - 2) / 2; // 2 is the size of one element
short[] val = new short[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadInt16();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI2):
{
int count = (int)(memStream.Length - 2) / 2; // 2 is the size of one element
ushort[] val = new ushort[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadUInt16();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_I4):
{
int count = (int)(memStream.Length - 2) / 4; // 2 is the size of one element
int[] val = new int[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadInt32();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI4):
{
int count = (int)(memStream.Length - 2) / 4; // size of one element
uint[] val = new uint[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadUInt32();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_I8):
{
int count = (int)(memStream.Length - 2) / Native.BitsPerByte; // size of one element
long[] val = new long[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadInt64();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_UI8):
{
int count = (int)(memStream.Length - 2) / Native.BitsPerByte; // size of one element
ulong[] val = new ulong[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadUInt64();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_R4):
{
int count = (int)(memStream.Length - 2) / 4; // size of one element
float[] val = new float[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadSingle();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_R8):
{
int count = (int)(memStream.Length - 2) / Native.BitsPerByte; // size of one element
double[] val = new double[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadDouble();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_DATE):
{
int count = (int)(memStream.Length - 2) / Native.BitsPerByte; // size of one element
DateTime[] val = new DateTime[count];
for (int i = 0; i < count; i++)
{
val[i] = DateTime.FromOADate(br.ReadDouble());
}
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_BOOL):
{
int count = (int)(memStream.Length - 2); // size of one element
bool[] val = new bool[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadBoolean();
return val;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_DECIMAL):
{
int count = (int)((memStream.Length - 2) / Native.SizeOfDecimal); // size of one element
decimal[] val = new decimal[count];
for (int i = 0; i < count; i++)
val[i] = br.ReadDecimal();
return val;
}
case (VarEnum.VT_I1):
return br.ReadChar();
case (VarEnum.VT_UI1):
return br.ReadByte();
case (VarEnum.VT_I2):
return br.ReadInt16();
case (VarEnum.VT_UI2):
return br.ReadUInt16();
case (VarEnum.VT_I4):
return br.ReadInt32();
case (VarEnum.VT_UI4):
return br.ReadUInt32();
case (VarEnum.VT_I8):
return br.ReadInt64();
case (VarEnum.VT_UI8):
return br.ReadUInt64();
case (VarEnum.VT_R4):
return br.ReadSingle();
case (VarEnum.VT_R8):
return br.ReadDouble();
case (VarEnum.VT_DATE):
return DateTime.FromOADate(br.ReadDouble());
case (VarEnum.VT_BOOL):
return br.ReadBoolean();
case (VarEnum.VT_DECIMAL):
return br.ReadDecimal();
case (VarEnum.VT_BSTR):
{
byte[] bytestring = br.ReadBytes((int)memStream.Length);
return System.Text.Encoding.Unicode.GetString(bytestring);
}
default:
{
throw new InvalidOperationException(SR.InvalidEpInIsf);
}
}
}
}
#if OLD_ISF
/// <summary>
/// Saves all elements in this list in the stream passed with the tags being generated based on the GuidList
/// by the caller and using compressionAlgorithm as the preferred algorith identifier. For ExtendedPropertyCollection associated
/// with Ink, drawing attributes and Point properties, we need to write the tag while saving them and hence
/// fTag param is true. For strokes, the Tag is stored in the stroke descriptor and hence we don't store the
/// tag
/// </summary>
/// <param name="attributes">Custom attributes to encode</param>
/// <param name="stream">If stream is null, then size is calculated only.</param>
/// <param name="guidList"></param>
/// <param name="compressionAlgorithm"></param>
/// <param name="fTag"></param>
/// <returns></returns>
#else
/// <summary>
/// Saves all elements in this list in the stream passed with the tags being generated based on the GuidList
/// by the caller and using compressionAlgorithm as the preferred algorith identifier. For ExtendedPropertyCollection associated
/// with Ink, drawing attributes and Point properties, we need to write the tag while saving them and hence
/// fTag param is true. For strokes, the Tag is stored in the stroke descriptor and hence we don't store the
/// tag
/// </summary>
/// <param name="attributes">Custom attributes to encode</param>
/// <param name="stream">If stream is null, then size is calculated only.</param>
/// <param name="guidList"></param>
/// <param name="compressionAlgorithm"></param>
/// <param name="fTag"></param>
#endif
internal static uint EncodeAsISF(ExtendedPropertyCollection attributes, Stream stream, GuidList guidList, byte compressionAlgorithm, bool fTag)
{
uint cbWrite = 0;
for (int i = 0; i < attributes.Count; i++)
{
ExtendedProperty prop = attributes[i];
using (MemoryStream localStream = new MemoryStream(10)) //reasonable default
{
ExtendedPropertySerializer.EncodeToStream(prop, localStream);
byte[] data = localStream.ToArray();
cbWrite += ExtendedPropertySerializer.EncodeAsISF(prop.Id, data, stream, guidList, compressionAlgorithm, fTag);
}
}
return cbWrite;
}
/// <summary>
/// Retrieve the guids for the custom attributes that are not known by
/// the v1 ISF decoder
/// </summary>
/// <param name="attributes"></param>
/// <param name="count">count of guids returned (can be less than return.Length</param>
/// <returns></returns>
internal static Guid[] GetUnknownGuids(ExtendedPropertyCollection attributes, out int count)
{
Guid[] guids = new Guid[attributes.Count];
count = 0;
for (int x = 0; x < attributes.Count; x++)
{
ExtendedProperty attribute = attributes[x];
if (0 == GuidList.FindKnownTag(attribute.Id))
{
guids[count++] = attribute.Id;
}
}
return guids;
}
#region Key/Value pair validation helpers
/// <summary>
/// Validates the data to be associated with a ExtendedProperty id
/// </summary>
/// <param name="id">ExtendedProperty identifier</param>
/// <param name="value">data</param>
/// <remarks>Ignores Ids that are not known (e.g. ExtendedProperties)</remarks>
internal static void Validate(Guid id, object value)
{
if (id == Guid.Empty)
{
throw new ArgumentException(SR.InvalidGuid);
}
if (id == KnownIds.Color)
{
if (!(value is System.Windows.Media.Color))
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(System.Windows.Media.Color)), "value");
}
}
// int attributes
else if (id == KnownIds.CurveFittingError)
{
if (!(value.GetType() == typeof(int)))
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(int)), "value");
}
}
else if (id == KnownIds.DrawingFlags)
{
// ignore validation of flags
if (value.GetType() != typeof(DrawingFlags))
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(DrawingFlags)), "value");
}
}
else if (id == KnownIds.StylusTip)
{
Type valueType = value.GetType();
bool fStylusTipType = ( valueType == typeof(StylusTip) );
bool fIntType = ( valueType == typeof(int) );
if ( !fStylusTipType && !fIntType )
{
throw new ArgumentException(SR.Format(SR.InvalidValueType1, typeof(StylusTip), typeof(int)), "value");
}
else if ( !StylusTipHelper.IsDefined((StylusTip)value) )
{
throw new ArgumentException(SR.Format(SR.InvalidValueOfType, value, typeof(StylusTip)), "value");
}
}
else if (id == KnownIds.StylusTipTransform)
{
//
// StylusTipTransform gets serialized as a String, but at runtime is a Matrix
//
Type t = value.GetType();
if ( t != typeof(String) && t != typeof(Matrix) )
{
throw new ArgumentException(SR.Format(SR.InvalidValueType1, typeof(String), typeof(Matrix)), "value");
}
else if ( t == typeof(Matrix) )
{
Matrix matrix = (Matrix)value;
if ( !matrix.HasInverse )
{
throw new ArgumentException(SR.MatrixNotInvertible, "value");
}
if ( MatrixHelper.ContainsNaN(matrix))
{
throw new ArgumentException(SR.InvalidMatrixContainsNaN, "value");
}
if ( MatrixHelper.ContainsInfinity(matrix))
{
throw new ArgumentException(SR.InvalidMatrixContainsInfinity, "value");
}
}
}
else if (id == KnownIds.IsHighlighter)
{
if ( value.GetType() != typeof(bool))
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(bool)), "value");
}
}
else if ( id == KnownIds.StylusHeight || id == KnownIds.StylusWidth )
{
if ( value.GetType() != typeof(double) )
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(double)), "value");
}
double dVal = (double)value;
if (id == KnownIds.StylusHeight)
{
if ( Double.IsNaN(dVal) || dVal < DrawingAttributes.MinHeight || dVal > DrawingAttributes.MaxHeight)
{
throw new ArgumentOutOfRangeException("value", SR.InvalidDrawingAttributesHeight);
}
}
else
{
if (Double.IsNaN(dVal) || dVal < DrawingAttributes.MinWidth || dVal > DrawingAttributes.MaxWidth)
{
throw new ArgumentOutOfRangeException("value", SR.InvalidDrawingAttributesWidth);
}
}
}
else if ( id == KnownIds.Transparency )
{
if ( value.GetType() != typeof(byte) )
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(byte)), "value");
}
double dVal = (double)value;
}
else
{
if ( !UsesEmbeddedTypeInformation(id) )
{
// if this guid used the legacy internal attribute persistence APIs,
// then it doesn't include embedded type information (it's always a byte array)
if ( value.GetType() != typeof(byte[]) )
{
throw new ArgumentException(SR.Format(SR.InvalidValueType, typeof(byte[])), "value");
}
}
else
{
// if there is any unsupported type, this call will throw.
VarEnum varEnum = SerializationHelper.ConvertToVarEnum(value.GetType(), true);
switch (varEnum)
{
case (VarEnum.VT_ARRAY | VarEnum.VT_I1)://8208
case (VarEnum.VT_I1)://16
case (VarEnum.VT_ARRAY | VarEnum.VT_DATE)://8199
case (VarEnum.VT_DATE)://7
{
//we have a char or char[], datetime or datetime[],
//we need to write them to a Stream using a BinaryWriter
//to see if an exception is thrown so that the exception
//happens now, and not at serialization time...
using (MemoryStream stream = new MemoryStream(32))//reasonable default
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
try
{
switch (varEnum)
{
case (VarEnum.VT_ARRAY | VarEnum.VT_I1)://8208
{
writer.Write((char[])value);
break;
}
case (VarEnum.VT_I1)://16
{
writer.Write((char)value);
break;
}
case (VarEnum.VT_ARRAY | VarEnum.VT_DATE)://8199
{
DateTime[] data = (DateTime[])value;
for (int i = 0; i < data.Length; i++)
writer.Write(data[i].ToOADate());
break;
}
case (VarEnum.VT_DATE)://7
{
DateTime data = (DateTime)value;
writer.Write(data.ToOADate());
break;
}
default:
{
Debug.Assert(false, "Missing case statement!");
break;
}
}
}
catch (ArgumentException ex)
{
//catches bad char & char[]
throw new ArgumentException(SR.InvalidDataInISF, ex);
}
catch (OverflowException ex)
{
//catches bad DateTime
throw new ArgumentException(SR.InvalidDataInISF, ex);
}
}
}
break;
}
//do nothing in the default case...
}
}
return;
}
}
#endregion // Key/Value pair validation helpers
}
}
|