File: MS\Internal\Ink\InkSerializedFormat\InkSerializer.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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 MS.Utility;
using System;
using System.Diagnostics;
using System.Security;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Input;
using System.Windows.Ink;
using MS.Internal.IO.Packaging;
 
using SR=MS.Internal.PresentationCore.SR;
 
namespace MS.Internal.Ink.InkSerializedFormat
{
    internal class StrokeCollectionSerializer
    {
        #region Constants (Static Fields)
        internal const double AvalonToHimetricMultiplier = 2540.0d / 96.0d;
        internal const double HimetricToAvalonMultiplier = 96.0d / 2540.0d;
        internal static readonly TransformDescriptor IdentityTransformDescriptor;
 
        static StrokeCollectionSerializer()
        {
            TransformDescriptor transformDescriptor = new TransformDescriptor();
            transformDescriptor.Transform[0] = 1.0f;
            transformDescriptor.Tag = KnownTagCache.KnownTagIndex.TransformIsotropicScale;
            transformDescriptor.Size = 1;
            StrokeCollectionSerializer.IdentityTransformDescriptor = transformDescriptor;
        }
        #endregion
 
        #region Constructors
 
        // disable default constructor
        private StrokeCollectionSerializer() { }
 
        /// <summary>
        /// Initialize the Ink serializer
        /// </summary>
        /// <param name="coreStrokes">Pointer to the core stroke collection - avoids recreation of collections</param>
        internal StrokeCollectionSerializer(StrokeCollection coreStrokes)
        {
            _coreStrokes = coreStrokes;
        }
 
        #endregion
 
        #region Public Fields
 
        internal PersistenceFormat CurrentPersistenceFormat = PersistenceFormat.InkSerializedFormat;
        internal CompressionMode CurrentCompressionMode = CompressionMode.Compressed;
        internal System.Collections.Generic.List<int> StrokeIds = null;
        #endregion
 
        #region Decoding
 
        #region Public Methods
 
 
        /// <summary>
        /// Loads a Ink object from a spcified byte array in the form of Ink Serialzied Format
        /// This method checks for the 'base64:' prefix in the byte[] because that is how V1
        /// saved ISF
        /// </summary>
        /// <param name="inkData"></param>
        internal void DecodeISF(Stream inkData)
        {
            try
            {
                // First examine the input data header
                bool isBase64;
                bool isGif;
                uint cbData;
 
                ExamineStreamHeader(inkData, out isBase64, out isGif, out cbData);
                if (isBase64)
                {
                    //
                    // this is a funky tablet v1 based byte[] that is base64 encoded...
                    // each 4 bytes in this array corresponds to 3 bytes of ISF data.
                    // EXCEPT the first 7 bytes which are saved with the value
                    // 'base64:' and must not be base64 decoded.
                    // and the last null terminator (if present)
                    //
                    //  The following code does two things:
                    //  1) Convert each byte to a char so it can be base64 decoded
                    //  2) Strips out the first 7 resulting characters
                    //
                    int isfBase64PrefixLength = Base64HeaderBytes.Length;
                    // the previous call to ExamineStreamHeader guarantees that inkData.Length > isfBase64PrefixLength
                    System.Diagnostics.Debug.Assert(inkData.Length > isfBase64PrefixLength);
 
                    inkData.Position = (long)isfBase64PrefixLength;
                    List<char> charData = new List<char>((int)inkData.Length);
                    int intByte = inkData.ReadByte();
                    while (intByte != -1)
                    {
                        byte b = (byte)intByte;
                        charData.Add((char)b);
                        intByte = inkData.ReadByte();
                    }
 
                    if (0 == (byte)(charData[charData.Count - 1]))
                    {
                        //strip the null terminator
                        charData.RemoveAt(charData.Count - 1);
                    }
 
                    char[] chars = charData.ToArray();
                    byte[] isfData = Convert.FromBase64CharArray(chars, 0, chars.Length);
                    MemoryStream ms = new MemoryStream(isfData);
                    if (IsGIFData(ms))
                    {
                        DecodeRawISF(SystemDrawingHelper.GetCommentFromGifStream(ms));
                    }
                    else
                    {
                        DecodeRawISF(ms);
                    }
                }
                else if (true == isGif)
                {
                    DecodeRawISF(SystemDrawingHelper.GetCommentFromGifStream(inkData));
                }
                else
                {
                    DecodeRawISF(inkData);
                }
            }
#if DEBUG
            catch (ArgumentException ex)
            {
                //only include an inner exception in debug builds
                throw new ArgumentException(SR.IsfOperationFailed, ex);
            }
            catch (InvalidOperationException ex)
            {
                //only include an inner exception in debug builds
                throw new ArgumentException(SR.IsfOperationFailed, ex);
            }
            catch (IndexOutOfRangeException ex)
            {
                //only include an inner exception in debug builds
                throw new ArgumentException(SR.IsfOperationFailed, ex);
            }
            catch (NullReferenceException ex)
            {
                //only include an inner exception in debug builds
                throw new ArgumentException(SR.IsfOperationFailed, ex);
            }
            catch (EndOfStreamException ex)
            {
                //only include an inner exception in debug builds
                throw new ArgumentException(SR.IsfOperationFailed, ex);
            }
            catch (OverflowException ex)
            {
                //only include an inner exception in debug builds
                throw new ArgumentException(SR.IsfOperationFailed, ex);
            }
#else
            catch (ArgumentException)
            {
                throw new ArgumentException(SR.IsfOperationFailed, "stream");//stream comes from StrokeCollection.ctor()
            }
            catch (InvalidOperationException)
            {
                throw new ArgumentException(SR.IsfOperationFailed, "stream");//stream comes from StrokeCollection.ctor()
            }
            catch (IndexOutOfRangeException)
            {
                throw new ArgumentException(SR.IsfOperationFailed, "stream");//stream comes from StrokeCollection.ctor()
            }
            catch (NullReferenceException)
            {
                throw new ArgumentException(SR.IsfOperationFailed, "stream");//stream comes from StrokeCollection.ctor()
            }
            catch (EndOfStreamException)
            {
                throw new ArgumentException(SR.IsfOperationFailed, "stream");//stream comes from StrokeCollection.ctor()
            }
            catch (OverflowException)
            {
                throw new ArgumentException(SR.IsfOperationFailed, "stream");//stream comes from StrokeCollection.ctor()
            }
#endif
        }
 
        #endregion
 
        #region Private Methods
 
        /// <summary>
        /// Loads the strokeIds from the stream, we need to do this to decrement the count of bytes
        /// </summary>
        internal uint LoadStrokeIds(Stream isfStream, uint cbSize)
        {
            if (0 == cbSize)
                return 0;
 
            uint cb;
            uint cbTotal = cbSize;
 
            // First decode the no of ids
            uint count;
 
            cb = SerializationHelper.Decode(isfStream, out count);
            if (cb > cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"), "isfStream");
 
            cbTotal -= cb;
            if (0 == count)
                return (cbSize - cbTotal);
 
            cb = cbTotal;
 
            byte[] inputdata = new byte[cb];
 
            // read the stream
            uint bytesRead = StrokeCollectionSerializer.ReliableRead(isfStream, inputdata, cb);
            if (cb != bytesRead)
            {
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Read different size from stream then expected"), "isfStream");
            }
            cbTotal -= cb;
 
            if (0 != cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"), "isfStream");
 
            return cbSize;
        }
 
 
        private bool IsGIFData(Stream inkdata)
        {
            Debug.Assert(inkdata != null);
            long currentPosition = inkdata.Position;
            try
            {
                return ((byte)inkdata.ReadByte() == 'G' &&
                        (byte)inkdata.ReadByte() == 'I' &&
                        (byte)inkdata.ReadByte() == 'F');
            }
            finally
            {
                //reset position
                inkdata.Position = currentPosition;
            }
        }
 
        private void ExamineStreamHeader(Stream inkdata, out bool fBase64, out bool fGif, out uint cbData)
        {
            fGif = false;
            cbData = 0;
            fBase64 = false;
 
            if (inkdata.Length >= 7)
            {
                fBase64 = IsBase64Data(inkdata);
            }
 
            // Check for RAW gif
            if (!fBase64 && inkdata.Length >= 3)
            {
                fGif = IsGIFData(inkdata);
            }
 
            return;
        }
 
        private static ReadOnlySpan<byte> Base64HeaderBytes => [(byte)'b',
                                                                (byte)'a',
                                                                (byte)'s',
                                                                (byte)'e',
                                                                (byte)'6',
                                                                (byte)'4',
                                                                (byte)':'];
 
#if OLD_ISF
        /// <summary>
        /// Takes an ISF byte[] and populates the StrokeCollection
        ///  attached to this StrokeCollectionSerializer.
        /// </summary>
        /// <param name="inkdata">a byte[] of the raw isf to decode</param>
#else
        /// <summary>
        /// Takes an ISF Stream and populates the StrokeCollection
        ///  attached to this StrokeCollectionSerializer.
        /// </summary>
        /// <param name="inputStream">a Stream the raw isf to decode</param>
#endif
        private void DecodeRawISF(Stream inputStream)
        {
            Debug.Assert(inputStream != null);
 
            KnownTagCache.KnownTagIndex isfTag;
            uint remainingBytesInStream;
            uint bytesDecodedInCurrentTag = 0;
            bool strokeDescriptorBlockDecoded = false;
            bool drawingAttributesBlockDecoded = false;
            bool metricBlockDecoded = false;
            bool transformDecoded = false;
            uint strokeDescriptorTableIndex = 0;
            uint oldStrokeDescriptorTableIndex = 0xFFFFFFFF;
            uint drawingAttributesTableIndex = 0;
            uint oldDrawingAttributesTableIndex = 0xFFFFFFFF;
            uint metricDescriptorTableIndex = 0;
            uint oldMetricDescriptorTableIndex = 0xFFFFFFFF;
            uint transformTableIndex = 0;
            uint oldTransformTableIndex = 0xFFFFFFFF;
            GuidList guidList = new GuidList();
            int strokeIndex = 0;
 
            StylusPointDescription currentStylusPointDescription = null;
            Matrix currentTabletToInkTransform = Matrix.Identity;
 
            _strokeDescriptorTable = new System.Collections.Generic.List<StrokeDescriptor>();
            _drawingAttributesTable = new System.Collections.Generic.List<DrawingAttributes>();
            _transformTable = new System.Collections.Generic.List<TransformDescriptor>();
            _metricTable = new System.Collections.Generic.List<MetricBlock>();
 
            // First make sure this ink is empty
            if (0 != _coreStrokes.Count || _coreStrokes.ExtendedProperties.Count != 0)
            {
                throw new InvalidOperationException(ISFDebugMessage("ISF decoder cannot operate on non-empty ink container"));
            }
#if OLD_ISF
            //
            // store a compressor reference at this scope, if it is needed (if there is a compresson header) and
            // therefore instanced during this routine, we will dispose of it
            // in the finally block
            //
            Compressor compressor = null;
 
            try
            {
#endif
 
            // First read the isfTag
            uint uiTag;
            uint localBytesDecoded = SerializationHelper.Decode(inputStream, out uiTag);
            if (0x00 != uiTag)
                throw new ArgumentException(SR.InvalidStream);
 
            // Now read the size of the stream
            localBytesDecoded = SerializationHelper.Decode(inputStream, out remainingBytesInStream);
            ISFDebugTrace("Decoded Stream Size in Bytes: " + remainingBytesInStream.ToString());
            if (0 == remainingBytesInStream)
                return;
 
            while (0 < remainingBytesInStream)
            {
                bytesDecodedInCurrentTag = 0;
 
                // First read the isfTag
                localBytesDecoded = SerializationHelper.Decode(inputStream, out uiTag);
                isfTag = (KnownTagCache.KnownTagIndex)uiTag;
                if (remainingBytesInStream >= localBytesDecoded)
                    remainingBytesInStream -= localBytesDecoded;
                else
                {
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                }
 
                ISFDebugTrace("Decoding Tag: " + ((KnownTagCache.KnownTagIndex)isfTag).ToString());
                switch (isfTag)
                {
                    case KnownTagCache.KnownTagIndex.GuidTable:
                    case KnownTagCache.KnownTagIndex.DrawingAttributesTable:
                    case KnownTagCache.KnownTagIndex.DrawingAttributesBlock:
                    case KnownTagCache.KnownTagIndex.StrokeDescriptorTable:
                    case KnownTagCache.KnownTagIndex.StrokeDescriptorBlock:
                    case KnownTagCache.KnownTagIndex.MetricTable:
                    case KnownTagCache.KnownTagIndex.MetricBlock:
                    case KnownTagCache.KnownTagIndex.TransformTable:
                    case KnownTagCache.KnownTagIndex.ExtendedTransformTable:
                    case KnownTagCache.KnownTagIndex.Stroke:
                    case KnownTagCache.KnownTagIndex.CompressionHeader:
                    case KnownTagCache.KnownTagIndex.PersistenceFormat:
                    case KnownTagCache.KnownTagIndex.HimetricSize:
                    case KnownTagCache.KnownTagIndex.StrokeIds:
                        {
                            localBytesDecoded = SerializationHelper.Decode(inputStream, out bytesDecodedInCurrentTag);
                            if (remainingBytesInStream < (localBytesDecoded + bytesDecodedInCurrentTag))
                            {
                                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"), "inputStream");
                            }
 
                            remainingBytesInStream -= localBytesDecoded;
 
                            // Based on the isfTag figure out what information we're loading
                            switch (isfTag)
                            {
                                case KnownTagCache.KnownTagIndex.GuidTable:
                                    {
                                        // Load guid Table
                                        localBytesDecoded = guidList.Load(inputStream, bytesDecodedInCurrentTag);
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.DrawingAttributesTable:
                                    {
                                        // Load drawing attributes table
                                        localBytesDecoded = LoadDrawAttrsTable(inputStream, guidList, bytesDecodedInCurrentTag);
                                        drawingAttributesBlockDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.DrawingAttributesBlock:
                                    {
                                        //initialize to V1 defaults, we do it this way as opposed
                                        //to dr.DrawingFlags = 0 because this was a perf hot spot
                                        //and instancing the epc first mitigates it
                                        ExtendedPropertyCollection epc = new ExtendedPropertyCollection();
                                        epc.Add(KnownIds.DrawingFlags, DrawingFlags.Polyline);
                                        DrawingAttributes dr = new DrawingAttributes(epc);
                                        localBytesDecoded = DrawingAttributeSerializer.DecodeAsISF(inputStream, guidList, bytesDecodedInCurrentTag, dr);
 
                                        _drawingAttributesTable.Add(dr);
                                        drawingAttributesBlockDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.StrokeDescriptorTable:
                                    {
                                        // Load stroke descriptor table
                                        localBytesDecoded = DecodeStrokeDescriptorTable(inputStream, bytesDecodedInCurrentTag);
                                        strokeDescriptorBlockDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.StrokeDescriptorBlock:
                                    {
                                        // Load a single stroke descriptor
                                        localBytesDecoded = DecodeStrokeDescriptorBlock(inputStream, bytesDecodedInCurrentTag);
                                        strokeDescriptorBlockDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.MetricTable:
                                    {
                                        // Load Metric Table
                                        localBytesDecoded = DecodeMetricTable(inputStream, bytesDecodedInCurrentTag);
                                        metricBlockDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.MetricBlock:
                                    {
                                        // Load a single Metric Block
                                        MetricBlock blk;
 
                                        localBytesDecoded = DecodeMetricBlock(inputStream, bytesDecodedInCurrentTag, out blk);
                                        _metricTable.Clear();
                                        _metricTable.Add(blk);
                                        metricBlockDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.TransformTable:
                                    {
                                        // Load Transform Table
                                        localBytesDecoded = DecodeTransformTable(inputStream, bytesDecodedInCurrentTag, false);
                                        transformDecoded = true;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.ExtendedTransformTable:
                                    {
                                        // non-double transform table should have already been loaded
                                        if (!transformDecoded)
                                        {
                                            throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                                        }
 
                                        // Load double-sized Transform Table
                                        localBytesDecoded = DecodeTransformTable(inputStream, bytesDecodedInCurrentTag, true);
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.PersistenceFormat:
                                    {
                                        uint fmt;
 
                                        localBytesDecoded = SerializationHelper.Decode(inputStream, out fmt);
                                        // Set the appropriate persistence information
                                        if (0 == fmt)
                                        {
                                            CurrentPersistenceFormat = PersistenceFormat.InkSerializedFormat;
                                        }
                                        else if (0x00000001 == fmt)
                                        {
                                            CurrentPersistenceFormat = PersistenceFormat.Gif;
                                        }
 
 
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.HimetricSize:
                                    {
                                        // Loads the Hi Metric Size for Fortified GIFs
                                        int sz;
 
                                        localBytesDecoded = SerializationHelper.SignDecode(inputStream, out sz);
                                        if (localBytesDecoded > remainingBytesInStream)
                                            throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
 
                                        _himetricSize.X = (double)sz;
                                        localBytesDecoded += SerializationHelper.SignDecode(inputStream, out sz);
 
                                        _himetricSize.Y = (double)sz;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.CompressionHeader:
                                    {
#if OLD_ISF
                                        byte[] data = new byte[bytesDecodedInCurrentTag];
 
                                        // read the header from the stream
                                        uint bytesRead = StrokeCollectionSerializer.ReliableRead(inputStream, data, bytesDecodedInCurrentTag);
                                        if (bytesDecodedInCurrentTag != bytesRead)
                                        {
                                            throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Read different size from stream then expected"), "isfStream");
                                        }
 
                                        uint size = bytesDecodedInCurrentTag;
                                        compressor = new Compressor(data, ref size);
                                        // in case the actual number of bytes read by the compressor
                                        //      is less than the encoder had expected (e.g. compression
                                        //      header was encoded as 10 bytes, but only 7 bytes were read)
                                        //      then we don't want to adjust the stream position because
                                        //      there are likely other following tags that are encoded
                                        //      after the compression tag. This should never happen,
                                        //      so just fail if the compressor is broken or the ISF is
                                        //      corrupted.
                                        if (size != bytesDecodedInCurrentTag)
                                        {
                                            throw new InvalidOperationException(ISFDebugMessage("Compressor intialization reported inconsistent size"));
                                        }
#else
                                        //just advance the inputstream position, we don't need
                                        //no compression header in the new isf decoding
                                        inputStream.Seek(bytesDecodedInCurrentTag, SeekOrigin.Current);
#endif
                                        localBytesDecoded = bytesDecodedInCurrentTag;
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.StrokeIds:
                                    {
                                        localBytesDecoded = LoadStrokeIds(inputStream, bytesDecodedInCurrentTag);
                                        break;
                                    }
 
                                case KnownTagCache.KnownTagIndex.Stroke:
                                    {
                                        ISFDebugTrace("   Decoding Stroke Id#(" + (strokeIndex + 1).ToString() + ")");
 
                                        StrokeDescriptor strokeDescriptor = null;
 
                                        // Load the stroke descriptor based on the index from the list of unique
                                        // stroke descriptors
                                        if (strokeDescriptorBlockDecoded)
                                        {
                                            if (oldStrokeDescriptorTableIndex != strokeDescriptorTableIndex)
                                            {
                                                if (_strokeDescriptorTable.Count <= strokeDescriptorTableIndex)
                                                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                                            }
 
                                            strokeDescriptor = _strokeDescriptorTable[(int)strokeDescriptorTableIndex];
                                        }
 
                                        // use new transform if the last transform is uninit'd or has changed
                                        if (oldTransformTableIndex != transformTableIndex)
                                        {
                                            // if transform was specified in the ISF stream
                                            if (transformDecoded)
                                            {
                                                if (_transformTable.Count <= transformTableIndex)
                                                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
 
                                                // Load the transform descriptor based on the index from the list of unique
                                                // transforn descriptors
                                                currentTabletToInkTransform = LoadTransform(_transformTable[(int)transformTableIndex]);
                                            }
 
                                            oldTransformTableIndex = transformTableIndex; // cache the transform by remembering the index
 
                                            // since ISF is stored in HIMETRIC, and we want to expose packet data
                                            //      as Avalon units, we'll update the convert the transform before loading the stroke
                                            currentTabletToInkTransform.Scale(StrokeCollectionSerializer.HimetricToAvalonMultiplier, StrokeCollectionSerializer.HimetricToAvalonMultiplier);
                                        }
 
                                        MetricBlock metricBlock = null;
 
                                        // Load the metric block based on the index from the list of unique metric blocks
                                        if (metricBlockDecoded)
                                        {
                                            if (oldMetricDescriptorTableIndex != metricDescriptorTableIndex)
                                            {
                                                if (_metricTable.Count <= metricDescriptorTableIndex)
                                                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                                            }
 
                                            metricBlock = _metricTable[(int)metricDescriptorTableIndex];
                                        }
 
                                        DrawingAttributes activeDrawingAttributes = null;
 
                                        // Load the drawing attributes based on the index from the list of unique drawing attributes
                                        if (drawingAttributesBlockDecoded)
                                        {
                                            if (oldDrawingAttributesTableIndex != drawingAttributesTableIndex)
                                            {
                                                if (_drawingAttributesTable.Count <= drawingAttributesTableIndex)
                                                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
 
                                                oldDrawingAttributesTableIndex = drawingAttributesTableIndex;
                                            }
                                            DrawingAttributes currDA = (DrawingAttributes)_drawingAttributesTable[(int)drawingAttributesTableIndex];
                                            //we always clone so we don't get strokes that share DAs, which can lead
                                            //to all sorts of unpredictable behavior
                                            activeDrawingAttributes = currDA.Clone();
                                        }
 
                                        // if we didn't find an existing da to use, instance a new one
                                        if (activeDrawingAttributes == null)
                                        {
                                            activeDrawingAttributes = new DrawingAttributes();
                                        }
 
                                        // Now create the StylusPacketDescription from the stroke descriptor and metric block
                                        if (oldMetricDescriptorTableIndex != metricDescriptorTableIndex || oldStrokeDescriptorTableIndex != strokeDescriptorTableIndex)
                                        {
                                            currentStylusPointDescription = BuildStylusPointDescription(strokeDescriptor, metricBlock, guidList);
                                            oldStrokeDescriptorTableIndex = strokeDescriptorTableIndex;
                                            oldMetricDescriptorTableIndex = metricDescriptorTableIndex;
                                        }
 
                                        // Load the stroke
                                        Stroke localStroke;
#if OLD_ISF
                                        localBytesDecoded = StrokeSerializer.DecodeStroke(inputStream, bytesDecodedInCurrentTag, guidList, strokeDescriptor, currentStylusPointDescription, activeDrawingAttributes, currentTabletToInkTransform, compressor, out localStroke);
#else
                                        localBytesDecoded = StrokeSerializer.DecodeStroke(inputStream, bytesDecodedInCurrentTag, guidList, strokeDescriptor, currentStylusPointDescription, activeDrawingAttributes, currentTabletToInkTransform, out localStroke);
#endif
 
                                        if (localStroke != null)
                                        {
                                            _coreStrokes.AddWithoutEvent(localStroke);
                                            strokeIndex++;
                                        }
                                        break;
                                    }
 
                                default:
                                    {
                                        throw new InvalidOperationException(ISFDebugMessage("Invalid ISF tag logic"));
                                    }
                            }
 
                            // if this isfTag's decoded size != expected size, then error out
                            if (localBytesDecoded != bytesDecodedInCurrentTag)
                            {
                                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                            }
 
                            break;
                        }
 
                    case KnownTagCache.KnownTagIndex.Transform:
                    case KnownTagCache.KnownTagIndex.TransformIsotropicScale:
                    case KnownTagCache.KnownTagIndex.TransformAnisotropicScale:
                    case KnownTagCache.KnownTagIndex.TransformRotate:
                    case KnownTagCache.KnownTagIndex.TransformTranslate:
                    case KnownTagCache.KnownTagIndex.TransformScaleAndTranslate:
                        {
                            // Load a single Transform Block
                            TransformDescriptor xform;
 
                            bytesDecodedInCurrentTag = DecodeTransformBlock(inputStream, isfTag, remainingBytesInStream, false, out xform);
                            transformDecoded = true;
                            _transformTable.Clear();
                            _transformTable.Add(xform);
                            break;
                        }
 
                    case KnownTagCache.KnownTagIndex.TransformTableIndex:
                        {
                            // Load the Index into the Transform Table which will be used by the stroke following this till
                            // a next different Index is found
                            bytesDecodedInCurrentTag = SerializationHelper.Decode(inputStream, out transformTableIndex);
                            break;
                        }
 
                    case KnownTagCache.KnownTagIndex.MetricTableIndex:
                        {
                            // Load the Index into the Metric Table which will be used by the stroke following this till
                            // a next different Index is found
                            bytesDecodedInCurrentTag = SerializationHelper.Decode(inputStream, out metricDescriptorTableIndex);
                            break;
                        }
 
                    case KnownTagCache.KnownTagIndex.DrawingAttributesTableIndex:
                        {
                            // Load the Index into the Drawing Attributes Table which will be used by the stroke following this till
                            // a next different Index is found
                            bytesDecodedInCurrentTag = SerializationHelper.Decode(inputStream, out drawingAttributesTableIndex);
                            break;
                        }
 
                    case KnownTagCache.KnownTagIndex.InkSpaceRectangle:
                        {
                            // Loads the Ink Space Rectangle information
                            bytesDecodedInCurrentTag = DecodeInkSpaceRectangle(inputStream, remainingBytesInStream);
                            break;
                        }
 
                    case KnownTagCache.KnownTagIndex.StrokeDescriptorTableIndex:
                        {
                            // Load the Index into the Stroke Descriptor Table which will be used by the stroke following this till
                            // a next different Index is found
                            bytesDecodedInCurrentTag = SerializationHelper.Decode(inputStream, out strokeDescriptorTableIndex);
                            break;
                        }
 
                    default:
                        {
                            if ((uint)isfTag >= KnownIdCache.CustomGuidBaseIndex || ((uint)isfTag >= KnownTagCache.KnownTagCount && ((uint)isfTag < (KnownTagCache.KnownTagCount + KnownIdCache.OriginalISFIdTable.Length))))
                            {
                                ISFDebugTrace("  CUSTOM_GUID=" + guidList.FindGuid(isfTag).ToString());
 
                                // Loads any custom property data
                                bytesDecodedInCurrentTag = remainingBytesInStream;
 
                                Guid guid = guidList.FindGuid(isfTag);
                                if (guid == Guid.Empty)
                                {
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Global Custom Attribute tag embedded in ISF stream does not match guid table"), "inkdata");
                                }
 
 
                                object data;
 
                                // load the custom property data from the stream (and decode the type)
                                localBytesDecoded = ExtendedPropertySerializer.DecodeAsISF(inputStream, bytesDecodedInCurrentTag, guidList, isfTag, ref guid, out data);
                                if (localBytesDecoded > bytesDecodedInCurrentTag)
                                {
                                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"), "inkdata");
                                }
 
 
                                // add the guid/data pair into the property collection (don't redecode the type)
                                _coreStrokes.ExtendedProperties[guid] = data;
                            }
                            else
                            {
                                // Skip objects that this library doesn't know about
                                // First read the size associated with this unknown isfTag
                                localBytesDecoded = SerializationHelper.Decode(inputStream, out bytesDecodedInCurrentTag);
                                if (remainingBytesInStream < (localBytesDecoded + bytesDecodedInCurrentTag))
                                {
                                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                                }
                                else
                                {
                                    inputStream.Seek(bytesDecodedInCurrentTag + localBytesDecoded, SeekOrigin.Current);
                                }
                            }
 
                            bytesDecodedInCurrentTag = localBytesDecoded;
                            break;
                        }
                }
                ISFDebugTrace("    Size = " + bytesDecodedInCurrentTag.ToString());
                if (bytesDecodedInCurrentTag > remainingBytesInStream)
                {
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"));
                }
 
                // update remaining ISF buffer length with decoded so far
                remainingBytesInStream -= bytesDecodedInCurrentTag;
            }
#if OLD_ISF
        }
        finally
        {
            if (null != compressor)
            {
                compressor.Dispose();
                compressor = null;
            }
        }
#endif
        if (0 != remainingBytesInStream)
            throw new ArgumentException(ISFDebugMessage("Invalid ISF data"), "inkdata");
    }
#if OLD_ISF
    /// <summary>
    /// Loads a DrawingAttributes Table from the stream and adds individual drawing attributes to the drawattr
    /// list passed
    /// </summary>
    /// <returns></returns>
#else
    /// <summary>
    /// Loads a DrawingAttributes Table from the stream and adds individual drawing attributes to the drawattr
    /// list passed
    /// </summary>
#endif
    private uint LoadDrawAttrsTable(Stream strm, GuidList guidList, uint cbSize)
    {
        _drawingAttributesTable.Clear();
 
        // First, allocate a temporary buffer and read the stream into it.
        // These will be compressed DRAW_ATTR structures.
        uint cbTotal = cbSize;
 
        // OK, now we count the number of DRAW_ATTRS compressed into this block
        uint cbDA = 0;
 
        while (cbTotal > 0)
        {
            // First read the size of the first drawing attributes block
            uint cb = SerializationHelper.Decode(strm, out cbDA);
 
            if (cbSize < cb)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
            cbTotal -= cb;
            if (cbTotal < cbDA)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
 
            // Create a new drawing attribute
            DrawingAttributes attributes = new DrawingAttributes();
            // pull off our defaults onthe drawing attribute as we need to
            //  respect what the ISF has.
            attributes.DrawingFlags = 0;
            cb = DrawingAttributeSerializer.DecodeAsISF(strm, guidList, cbDA, attributes);
 
            // Load the stream into this attribute
            if (cbSize < cbDA)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
            cbTotal -= cbDA;
 
            // Add this attribute to the global list
            _drawingAttributesTable.Add(attributes);
        }
 
        if (0 != cbTotal)
            throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
        return cbSize;
    }
 
    /// <summary>
    /// Reads and Decodes a stroke descriptor information from the stream. For details on how it is stored
    /// please refer the spec
    /// </summary>
    /// <param name="strm"></param>
    /// <param name="cbSize"></param>
    /// <param name="descr"></param>
    /// <returns></returns>
    private uint DecodeStrokeDescriptor(Stream strm, uint cbSize, out StrokeDescriptor descr)
    {
        descr = new StrokeDescriptor();
        if (0 == cbSize)
            return 0;
 
        uint cb;
        uint cbBlock = cbSize;
 
        while (cbBlock > 0)
        {
            // first read the tag
            KnownTagCache.KnownTagIndex tag;
            uint uiTag;
 
            cb = SerializationHelper.Decode(strm, out uiTag);
            tag = (KnownTagCache.KnownTagIndex)uiTag;
            if (cb > cbBlock)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            cbBlock -= cb;
            descr.Template.Add(tag);
 
            // If this is TAG_BUTTONS
            if (KnownTagCache.KnownTagIndex.Buttons == tag && cbBlock > 0)
            {
                uint cbButton;
 
                // Read the no. of buttons first
                cb = SerializationHelper.Decode(strm, out cbButton);
                if (cb > cbBlock)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
                cbBlock -= cb;
                descr.Template.Add((KnownTagCache.KnownTagIndex)cbButton);
                while (cbBlock > 0 && cbButton > 0)
                {
                    uint dw;
 
                    cb = SerializationHelper.Decode(strm, out dw);
                    if (cb > cbBlock)
                        throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
                    cbBlock -= cb;
                    cbButton--;
                    descr.Template.Add((KnownTagCache.KnownTagIndex)dw);
                }
            }
            else if (KnownTagCache.KnownTagIndex.StrokePropertyList == tag && cbBlock > 0)
            {
                // Usually stroke property comes last in the template. Hence everything below this is
                // are Tags for strokes extended properties
                while (cbBlock > 0)
                {
                    uint dw;
 
                    cb = SerializationHelper.Decode(strm, out dw);
                    if (cb > cbBlock)
                        throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
                    cbBlock -= cb;
                    descr.Template.Add((KnownTagCache.KnownTagIndex)dw);
                }
            }
            }
 
            if (0 != cbBlock)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            return cbSize;
        }
 
 
        /// <summary>
        /// Reads and Decodes a stroke descriptor information from the stream. For details on how it is stored
        /// please refer the spec
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="cbSize"></param>
        /// <returns></returns>
        private uint DecodeStrokeDescriptorBlock(Stream strm, uint cbSize)
        {
            _strokeDescriptorTable.Clear();
            if (0 == cbSize)
                return 0;
 
            StrokeDescriptor descr;
            uint cbRead = DecodeStrokeDescriptor(strm, cbSize, out descr);
 
            if (cbRead != cbSize)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            _strokeDescriptorTable.Add(descr);
            return cbRead;
        }
 
 
        /// <summary>
        /// Reads and Decodes a number of stroke descriptor information from the stream. For details on how they are stored
        /// please refer the spec
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="cbSize"></param>
        /// <returns></returns>
        private uint DecodeStrokeDescriptorTable(Stream strm, uint cbSize)
        {
            _strokeDescriptorTable.Clear();
            if (0 == cbSize)
                return 0;
 
            uint cb;                // Tracks the total no of bytes read from the stream
            uint cbTotal = cbSize;  // Tracks how many more bytes can be read from the stream for the table. Limited by cbSize
 
            while (cbTotal > 0)
            {
                // First decode the size of the next block
                uint cbBlock;
 
                cb = SerializationHelper.Decode(strm, out cbBlock);
                if (cb > cbTotal)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                cbTotal -= cb;
                if (cbBlock > cbTotal)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
 
                StrokeDescriptor descr;
 
                cb = DecodeStrokeDescriptor(strm, cbBlock, out descr);
                if (cb != cbBlock)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                cbTotal -= cb;
 
                // Add this stroke descriptor to the list of global stroke descriptors
                _strokeDescriptorTable.Add(descr);
            }
 
            if (0 != cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
            return cbSize;
        }
 
 
        /// <summary>
        /// Decodes metric table from the stream. For information on how they are stored in the stream, please refer to the spec.
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="cbSize"></param>
        /// <returns></returns>
        private uint DecodeMetricTable(Stream strm, uint cbSize)
        {
            _metricTable.Clear();
            if (cbSize == 0)
                return 0;
 
            uint cb;
            uint cbTotal = cbSize;
 
            // This data is a list of Metric block. Each block starts with size of the block. After that it contains an
            // array of Metric Entries. Each metric enty comprises of size of the entry, tag for the property and the metric
            // properties.
            while (cbTotal > 0)
            {
                // First read the size of the metric block
                uint dw;
 
                cb = SerializationHelper.Decode(strm, out dw);
                if (cb + dw > cbTotal)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                cbTotal -= cb;
 
                MetricBlock newblock;
 
                cb = DecodeMetricBlock(strm, dw, out newblock);
                if (cb != dw)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                cbTotal -= cb;
                _metricTable.Add(newblock);
            }
 
            if (0 != cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
            return cbSize;
        }
 
 
        /// <summary>
        /// Decodes a Metric Block from the stream. For information on how they are stored in the stream, please refer to the spec.
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="cbSize"></param>
        /// <param name="block"></param>
        /// <returns></returns>
        private uint DecodeMetricBlock(Stream strm, uint cbSize, out MetricBlock block)
        {
            // allocate the block
            block = new MetricBlock();
            if (cbSize == 0)
                return 0;
 
            uint cb;
            uint cbTotal = cbSize;
            uint size;
 
            while (cbTotal > 0)
            {
                // First decode the tag for this entry
                uint dw;
 
                cb = SerializationHelper.Decode(strm, out dw);
                if (cb > cbTotal)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                cbTotal -= cb;
 
                // Next read the size of the metric data
                cb = SerializationHelper.Decode(strm, out size);
                if (cb + size > cbTotal)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                cbTotal -= cb;
 
                // now create new metric entry
                MetricEntry entry = new MetricEntry();
 
                entry.Tag = (KnownTagCache.KnownTagIndex)dw;
 
                byte[] data = new byte[size];
 
                uint bytesRead = StrokeCollectionSerializer.ReliableRead(strm, data, size);
                cbTotal -= bytesRead;
 
                if ( bytesRead != size )
                {
                    // Make sure the bytes read are expected. If not, we should bail out.
                    // An exception will be thrown.
                    break;
                }
 
                entry.Data = data;
                block.AddMetricEntry(entry);
}
 
            if (0 != cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
            return cbSize;
        }
 
 
        /// <summary>
        /// Reads and Decodes a Table of Transform Descriptors from the stream. For information on how they are stored
        /// in the stream, please refer to the spec.
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="cbSize"></param>
        /// <param name="useDoubles"></param>
        /// <returns></returns>
        private uint DecodeTransformTable(Stream strm, uint cbSize, bool useDoubles)
        {
            // only clear the transform table if not using doubles
            //      (e.g. first pass through transform table)
            if (!useDoubles)
            {
                _transformTable.Clear();
            }
 
            if (0 == cbSize)
                return 0;
 
            uint cb;
            uint cbTotal = cbSize;
            int tableIndex = 0;
 
            while (cbTotal > 0)
            {
                KnownTagCache.KnownTagIndex tag;
                uint uiTag;
                cb = SerializationHelper.Decode(strm, out uiTag);
                tag = (KnownTagCache.KnownTagIndex)uiTag;
                if (cb > cbTotal)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
                cbTotal -= cb;
 
                TransformDescriptor xform;
 
                cb = DecodeTransformBlock(strm, tag, cbTotal, useDoubles, out xform);
                cbTotal -= cb;
                if (useDoubles)
                {
                    _transformTable[tableIndex] = xform;
                }
                else
                {
                    _transformTable.Add(xform);
                }
 
                tableIndex++;
            }
 
            if (0 != cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            return cbSize;
        }
 
        /// <summary>
        /// ReliableRead
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="buffer"></param>
        /// <param name="requestedCount"></param>
        /// <returns></returns>
        internal static uint ReliableRead(Stream stream, byte[] buffer, uint requestedCount)
        {
            if (stream == null ||
                buffer == null ||
                requestedCount > buffer.Length)
            {
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid argument passed to ReliableRead"));
            }
 
            // let's read the whole block into our buffer
            uint totalBytesRead = 0;
            while (totalBytesRead < requestedCount)
            {
                int bytesRead = stream.Read(buffer,
                                (int)totalBytesRead,
                                (int)(requestedCount - totalBytesRead));
                if (bytesRead == 0)
                {
                    break;
                }
                totalBytesRead += (uint)bytesRead;
            }
            return totalBytesRead;
        }
 
 
        /// <summary>
        /// Reads and Decodes a Transfrom Descriptor Block from the stream. For information on how it is stored in the stream,
        /// please refer to the spec.
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="tag"></param>
        /// <param name="cbSize"></param>
        /// <param name="useDoubles"></param>
        /// <param name="xform"></param>
        /// <returns></returns>
        private uint DecodeTransformBlock(Stream strm, KnownTagCache.KnownTagIndex tag, uint cbSize, bool useDoubles, out TransformDescriptor xform)
        {
            xform = new TransformDescriptor();
            xform.Tag = tag;
 
            uint cbRead = 0;
            uint cbTotal = cbSize;
 
            if (0 == cbSize)
                return 0;
 
            // samgeo - Presharp issue
            // 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 read from
#pragma warning disable 1634, 1691
#pragma warning disable 6518
            BinaryReader bw = new BinaryReader(strm);
 
            if (KnownTagCache.KnownTagIndex.TransformRotate == tag)
            {
                uint angle;
 
                cbRead = SerializationHelper.Decode(strm, out angle);
                if (cbRead > cbSize)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                xform.Transform[0] = (double)angle;
                xform.Size = 1;
            }
            else
            {
                if (tag == KnownTagCache.KnownTagIndex.TransformIsotropicScale)
                {
                    xform.Size = 1;
                }
                else if (tag == KnownTagCache.KnownTagIndex.TransformAnisotropicScale || tag == KnownTagCache.KnownTagIndex.TransformTranslate)
                {
                    xform.Size = 2;
                }
                else if (tag == KnownTagCache.KnownTagIndex.TransformScaleAndTranslate)
                {
                    xform.Size = 4;
                }
                else
                {
                    xform.Size = 6;
                }
 
                if (useDoubles)
                {
                    cbRead = xform.Size * Native.SizeOfDouble;
                }
                else
                {
                    cbRead = xform.Size * Native.SizeOfFloat;
                }
 
                if (cbRead > cbSize)
                    throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
 
                for (int i = 0; i < xform.Size; i++)
                {
                    if (useDoubles)
                    {
                        xform.Transform[i] = bw.ReadDouble();
                    }
                    else
                    {
                        xform.Transform[i] = (double)bw.ReadSingle();
                    }
                }
            }
 
            return cbRead;
#pragma warning restore 6518
#pragma warning restore 1634, 1691
        }
 
        /// <summary>
        /// Decodes Ink Space Rectangle information from the stream
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="cbSize"></param>
        /// <returns></returns>
        private uint DecodeInkSpaceRectangle(Stream strm, uint cbSize)
        {
            uint cb, cbRead = 0;
            uint cbTotal = cbSize;
            int data;
 
            //Left
            cb = SerializationHelper.SignDecode(strm, out data);
            if (cb > cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            cbTotal -= cb;
            cbRead += cb;
            _inkSpaceRectangle.X = data;
            if (cbRead > cbSize)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            //Top
            cb = SerializationHelper.SignDecode(strm, out data);
            if (cb > cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            cbTotal -= cb;
            cbRead += cb;
            _inkSpaceRectangle.Y = data;
            if (cbRead > cbSize)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            //Right
            cb = SerializationHelper.SignDecode(strm, out data);
            if (cb > cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            cbTotal -= cb;
            cbRead += cb;
            _inkSpaceRectangle.Width = data - _inkSpaceRectangle.Left;
            if (cbRead > cbSize)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            //Bottom
            cb = SerializationHelper.SignDecode(strm, out data);
            if (cb > cbTotal)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            cbTotal -= cb;
            cbRead += cb;
            _inkSpaceRectangle.Height = data - _inkSpaceRectangle.Top;
            if (cbRead > cbSize)
                throw new ArgumentException(ISFDebugMessage("Invalid ISF data"),"strm");
 
            return cbRead;
        }
 
 
        /// <summary>
        /// Creates a Matrix Information structure based on the transform descriptor
        /// </summary>
        /// <param name="tdrd"></param>
        /// <returns></returns>
        private Matrix LoadTransform(TransformDescriptor tdrd)
        {
            double M00 = 0.0f, M01 = 0.0f, M10 = 0.0f, M11 = 0.0f, M20 = 0.0f, M21 = 0.0f;
 
            if (KnownTagCache.KnownTagIndex.TransformIsotropicScale == tdrd.Tag)
            {
                M00 = M11 = tdrd.Transform[0];
            }
            else if (KnownTagCache.KnownTagIndex.TransformRotate == tdrd.Tag)
            {
                double dAngle = (tdrd.Transform[0] / 100) * (Math.PI / 180);
 
                M00 = M11 = Math.Cos(dAngle);
                M01 = Math.Sin(dAngle);
                if (M01 == 0.0f && M11 == 1.0f)
                {
                    //special case for 0 degree rotate transforms
                    //this is identity
                    M10 = 0.0f;
                }
                else
                {
                    M10 = -M11;
                }
            }
            else if (KnownTagCache.KnownTagIndex.TransformAnisotropicScale == tdrd.Tag)
            {
                M00 = tdrd.Transform[0];
                M11 = tdrd.Transform[1];
            }
            else if (KnownTagCache.KnownTagIndex.TransformTranslate == tdrd.Tag)
            {
                M20 = tdrd.Transform[0];
                M21 = tdrd.Transform[1];
            }
            else if (KnownTagCache.KnownTagIndex.TransformScaleAndTranslate == tdrd.Tag)
            {
                M00 = tdrd.Transform[0];
                M11 = tdrd.Transform[1];
                M20 = tdrd.Transform[2];
                M21 = tdrd.Transform[3];
            }
            else    // TAG_TRANSFORM
            {
                M00 = tdrd.Transform[0];
                M01 = tdrd.Transform[1];
                M10 = tdrd.Transform[2];
                M11 = tdrd.Transform[3];
                M20 = tdrd.Transform[4];
                M21 = tdrd.Transform[5];
            }
 
            return new Matrix(M00, M01, M10, M11, M20, M21);
        }
 
 
        /// <summary>
        /// Sets the Property Metrics for a property based on Tag and metric descriptor block
        /// </summary>
        /// <param name="guid"></param>
        /// <param name="tag"></param>
        /// <param name="block"></param>
        /// <returns></returns>
        private StylusPointPropertyInfo GetStylusPointPropertyInfo(Guid guid, KnownTagCache.KnownTagIndex tag, MetricBlock block)
        {
            int dw = 0;
            bool fSetDefault = false;
            uint cbEntry;
            // StylusPointPropertyInfo values that we need to read in.
            int minimum = 0;
            int maximum = 0;
            StylusPointPropertyUnit unit = StylusPointPropertyUnit.None;
            float resolution = 1.0f;
 
            // To begin with initialize the property metrics with respective default valuses
            // first check if this property belongs to optional list
            for (dw = 0; dw < 11; dw++)
            {
                if (MetricEntry.MetricEntry_Optional[dw].Tag == tag)
                {
                    minimum = MetricEntry.MetricEntry_Optional[dw].PropertyMetrics.Minimum;
                    maximum = MetricEntry.MetricEntry_Optional[dw].PropertyMetrics.Maximum;
                    resolution = MetricEntry.MetricEntry_Optional[dw].PropertyMetrics.Resolution;
                    unit = MetricEntry.MetricEntry_Optional[dw].PropertyMetrics.Unit;
                    fSetDefault = true;
                    break;
                }
            }
 
            if (false == fSetDefault)
            {
                // We will come here if the property is not found in the Optional List
                // All other cases, we will have only default values
                minimum = Int32.MinValue;
                maximum = Int32.MaxValue;
                unit = StylusPointPropertyUnit.None;
                resolution = 1.0f;
                fSetDefault = true;
            }
 
            // Now see if there is a valid MetricBlock. If there is one, update the PROPERTY_METRICS with
            // values from this Block
            if (null != block)
            {
                MetricEntry entry = block.GetMetricEntryList();
 
                while (null != entry)
                {
                    if (entry.Tag == tag)
                    {
                        cbEntry = 0;
 
                        int range;
 
                        using (MemoryStream strm = new MemoryStream(entry.Data))
                        {
                            // Decoded the Logical Min
                            cbEntry += SerializationHelper.SignDecode(strm, out range);
                            if (cbEntry >= entry.Size)
                            {
                                break; // return false;
                            }
 
                            minimum = range;
 
                            // Logical Max
                            cbEntry += SerializationHelper.SignDecode(strm, out range);
                            if (cbEntry >= entry.Size)
                            {
                                break; // return false;
                            }
 
                            maximum = range;
 
                            uint cb;
 
                            // Units
                            cbEntry += SerializationHelper.Decode(strm, out cb);
                            unit = (StylusPointPropertyUnit)cb;
                            if (cbEntry >= entry.Size)
                            {
                                break; // return false;
                            }
 
                            using (BinaryReader br = new BinaryReader(strm))
                            {
                                resolution = br.ReadSingle();
                                cbEntry += Native.SizeOfFloat;
                            }
                        }
 
                        break;
                    }
 
                    entry = entry.Next;
                }
            }
 
            // return a new StylusPointPropertyInfo
            return new StylusPointPropertyInfo( new StylusPointProperty(guid, StylusPointPropertyIds.IsKnownButton(guid)),
                                                minimum,
                                                maximum,
                                                unit,
                                                resolution);
        }
 
 
        /// <summary>
        /// Builds StylusPointDescription based on StrokeDescriptor and Metric Descriptor Block. Sometime Metric Descriptor block may contain
        /// metric information for properties which are not part of the stroke descriptor. They are simply ignored.
        /// </summary>
        /// <param name="strd"></param>
        /// <param name="block"></param>
        /// <param name="guidList"></param>
        /// <returns></returns>
        private StylusPointDescription BuildStylusPointDescription(StrokeDescriptor strd, MetricBlock block, GuidList guidList)
        {
            int cTags = 0;
            int packetPropertyCount = 0;
            uint buttonCount = 0;
            Guid[] buttonguids = null;
            System.Collections.Generic.List<KnownTagCache.KnownTagIndex> tags = null;
 
            // if strd is null, it means there is only default descriptor with X & Y
            if (null != strd)
            {
                tags = new System.Collections.Generic.List<KnownTagCache.KnownTagIndex>();
                while (cTags < strd.Template.Count)
                {
                    KnownTagCache.KnownTagIndex tag = (KnownTagCache.KnownTagIndex)strd.Template[cTags];
 
                    if (KnownTagCache.KnownTagIndex.Buttons == tag)
                    {
                        cTags++;
 
                        // The next item in the array is no of buttongs.
                        buttonCount = (uint)strd.Template[cTags];
                        cTags++;
 
                        // Currently we skip the the no of buttons as buttons is not implimented yet
                        buttonguids = new Guid[buttonCount];
                        for (uint u = 0; u < buttonCount; u++)
                        {
                            Guid guid = guidList.FindGuid(strd.Template[cTags]);
                            if (guid == Guid.Empty)
                            {
                                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Button guid tag embedded in ISF stream does not match guid table"),"strd");
                            }
 
                            buttonguids[(int)u] = guid;
                            cTags++;
                        }
                    }
                    else if (KnownTagCache.KnownTagIndex.StrokePropertyList == tag)
                    {
                        break; // since no more Packet properties can be stored
                    }
                    else
                    {
                        if (KnownTagCache.KnownTagIndex.NoX == tag ||
                            KnownTagCache.KnownTagIndex.NoY == tag)
                        {
                            throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF with NoX or NoY specified"), "strd");
                        }
 
                        tags.Add(strd.Template[cTags]);
                        packetPropertyCount++;
                        cTags++;
                    }
                }
            }
 
 
            List<StylusPointPropertyInfo> stylusPointPropertyInfos = new List<StylusPointPropertyInfo>();
            stylusPointPropertyInfos.Add(GetStylusPointPropertyInfo(KnownIds.X, (KnownTagCache.KnownTagIndex)((uint)KnownIdCache.KnownGuidBaseIndex + (uint)KnownIdCache.OriginalISFIdIndex.X), block));
            stylusPointPropertyInfos.Add(GetStylusPointPropertyInfo(KnownIds.Y, (KnownTagCache.KnownTagIndex)((uint)KnownIdCache.KnownGuidBaseIndex + (uint)KnownIdCache.OriginalISFIdIndex.Y), block));
            stylusPointPropertyInfos.Add(GetStylusPointPropertyInfo(KnownIds.NormalPressure, (KnownTagCache.KnownTagIndex)((uint)KnownIdCache.KnownGuidBaseIndex + (uint)KnownIdCache.OriginalISFIdIndex.NormalPressure), block));
 
            int pressureIndex = -1;
            if (tags != null)
            {
                for (int i = 0; i < tags.Count; i++)
                {
                    Guid guid = guidList.FindGuid(tags[i]);
                    if (guid == Guid.Empty)
                    {
                        throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Packet Description Property tag embedded in ISF stream does not match guid table"), "strd");
                    }
                    if (pressureIndex == -1 && guid == StylusPointPropertyIds.NormalPressure)
                    {
                        pressureIndex = i + 2; //x,y have already been accounted for
                        continue; //we've already added pressure (above)
                    }
 
                    stylusPointPropertyInfos.Add(GetStylusPointPropertyInfo(guid, tags[i], block));
                }
 
                if (null != buttonguids)
                {
                    //
                    // add the buttons to the end of the description if they exist
                    //
                    for (int i = 0; i < buttonguids.Length; i++)
                    {
                        StylusPointProperty buttonProperty = new StylusPointProperty(buttonguids[i], true);
                        StylusPointPropertyInfo buttonInfo = new StylusPointPropertyInfo(buttonProperty);
                        stylusPointPropertyInfos.Add(buttonInfo);
                    }
                }
            }
 
            return new StylusPointDescription(stylusPointPropertyInfos, pressureIndex);
        }
        #endregion
 
        #endregion // Decoding
 
        #region Encoding
 
        #region Public Methods
#if OLD_ISF
        /// <summary>
        /// This functions Saves the Ink as Ink Serialized Format based on the Compression code
        /// </summary>
        /// <returns>A byte[] with the encoded ISF</returns>
#else
        /// <summary>
        /// This functions Saves the Ink as Ink Serialized Format based on the Compression code
        /// </summary>
        /// <returns>A byte[] with the encoded ISF</returns>
#endif
        internal void EncodeISF(Stream outputStream)
        {
            _strokeLookupTable =
                new System.Collections.Generic.Dictionary<Stroke, StrokeLookupEntry>(_coreStrokes.Count);
 
            // Next go through all the strokes
            for (int i = 0; i < _coreStrokes.Count; i++)
            {
                _strokeLookupTable.Add(_coreStrokes[i], new StrokeLookupEntry());
            }
 
            // Initialize all Arraylists
            _strokeDescriptorTable = new List<StrokeDescriptor>(_coreStrokes.Count);
            _drawingAttributesTable = new List<DrawingAttributes>();
            _metricTable = new List<MetricBlock>();
            _transformTable = new List<TransformDescriptor>();
 
            using (MemoryStream localStream = new MemoryStream(_coreStrokes.Count * 125)) //reasonable default
            {
                GuidList guidList = BuildGuidList();
                uint cumulativeEncodedSize = 0;
                uint localEncodedSize = 0;
 
                byte xpData = (CurrentCompressionMode == CompressionMode.NoCompression) ? AlgoModule.NoCompression : AlgoModule.DefaultCompression;
                foreach (Stroke s in _coreStrokes)
                {
                    _strokeLookupTable[s].CompressionData = xpData;
 
                    //
                    // we need to get this data up front so that we can
                    // know if pressure was used (and thus if we need to add Pressure
                    // to the ISF packet description
                    //
                    int[][] isfReadyData;
                    bool shouldStorePressure;
                    s.StylusPoints.ToISFReadyArrays(out isfReadyData, out shouldStorePressure);
                    _strokeLookupTable[s].ISFReadyStrokeData = isfReadyData;
                    //
                    // this is our flag that ToISFReadyArrays sets if pressure was all default
                    //
                    _strokeLookupTable[s].StorePressure = shouldStorePressure;
                }
 
 
                // Store Ink space rectangle information if necessary and anything other than default
                if (_inkSpaceRectangle != new Rect())
                {
                    localEncodedSize = cumulativeEncodedSize;
 
                    Rect inkSpaceRectangle = _inkSpaceRectangle;
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.InkSpaceRectangle);
 
                    int i = (int)inkSpaceRectangle.Left;
 
                    cumulativeEncodedSize += SerializationHelper.SignEncode(localStream, i);
                    i = (int)inkSpaceRectangle.Top;
                    cumulativeEncodedSize += SerializationHelper.SignEncode(localStream, i);
                    i = (int)inkSpaceRectangle.Right;
                    cumulativeEncodedSize += SerializationHelper.SignEncode(localStream, i);
                    i = (int)inkSpaceRectangle.Bottom;
                    cumulativeEncodedSize += SerializationHelper.SignEncode(localStream, i);
 
                    // validate that the expected inkspace rectangle block in ISF was the actual size encoded
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("Encoded InkSpaceRectangle: size=" + localEncodedSize);
 
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // First prepare the compressor. Currently Compression is not supported.
                // Next write the persistence format information if anything other than ISF
                // Currently only ISF is implemented
                if (PersistenceFormat.InkSerializedFormat != CurrentPersistenceFormat)
                {
                    localEncodedSize = cumulativeEncodedSize;
 
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.PersistenceFormat);
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)SerializationHelper.VarSize((uint)CurrentPersistenceFormat));
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)CurrentPersistenceFormat);
 
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("Encoded PersistenceFormat: size=" + localEncodedSize);
 
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // Future enhancement: store any size information if necessary such as GIF image size
 
                // Now store the Custom Guids
                localEncodedSize = cumulativeEncodedSize;
                cumulativeEncodedSize += guidList.Save(localStream);
                localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                if (localEncodedSize != 0)
                    ISFDebugTrace("Encoded Custom Guid Table: size=" + localEncodedSize);
 
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
 
                // Now build the tables
                BuildTables(guidList);
 
                // first write the drawing attributes
                localEncodedSize = cumulativeEncodedSize;
                cumulativeEncodedSize += SerializeDrawingAttrsTable(localStream, guidList);
                localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                if (localEncodedSize != 0)
                    ISFDebugTrace("Encoded DrawingAttributesTable: size=" + localEncodedSize);
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
 
                // Next write the stroke descriptor table
                localEncodedSize = cumulativeEncodedSize;
                cumulativeEncodedSize += SerializePacketDescrTable(localStream);
                localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                if (localEncodedSize != 0)
                    ISFDebugTrace("Encoded Packet Description: size=" + localEncodedSize);
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
 
                // Write the metric table
                localEncodedSize = cumulativeEncodedSize;
                cumulativeEncodedSize += SerializeMetricTable(localStream);
                localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                if (localEncodedSize != 0)
                    ISFDebugTrace("Encoded Metric Table: size=" + localEncodedSize);
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
 
                // Write the transform table
                localEncodedSize = cumulativeEncodedSize;
                cumulativeEncodedSize += SerializeTransformTable(localStream);
                localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                if (localEncodedSize != 0)
                    ISFDebugTrace("Encoded Transform Table: size=" + localEncodedSize);
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
 
                // Save global ink properties
                if (_coreStrokes.ExtendedProperties.Count > 0)
                {
                    localEncodedSize = cumulativeEncodedSize;
                    cumulativeEncodedSize += ExtendedPropertySerializer.EncodeAsISF(_coreStrokes.ExtendedProperties, localStream, guidList, GetCompressionAlgorithm(), true);
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("Encoded Global Ink Attributes Table: size=" + localEncodedSize);
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // Save stroke ids
                localEncodedSize = cumulativeEncodedSize;
                cumulativeEncodedSize += SaveStrokeIds(_coreStrokes, localStream, false);
                localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                if (localEncodedSize != 0)
                    ISFDebugTrace("Encoded Stroke Id List: size=" + localEncodedSize);
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
 
                StoreStrokeData(localStream, guidList, ref cumulativeEncodedSize, ref localEncodedSize);
 
                ISFDebugTrace("Embedded ISF Stream size=" + cumulativeEncodedSize);
 
                // Now that all data has been written we need to prepend the stream
                long preEncodingPosition = outputStream.Position;
                uint cbFinal = SerializationHelper.Encode(outputStream, (uint)0x00);
 
                cbFinal += SerializationHelper.Encode(outputStream, cumulativeEncodedSize);
 
                //we have to use localStream to encode ISF because we have to place a variable byte 'size of isf' at the
                //beginning of the stream
                outputStream.Write(localStream.GetBuffer(), 0, (int)cumulativeEncodedSize);
                cbFinal += cumulativeEncodedSize;
 
                ISFDebugTrace("Final ISF Stream size=" + cbFinal);
 
                if (cbFinal != outputStream.Position - preEncodingPosition)
                {
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
            }
        }
 
#if OLD_ISF
        /// <Summary>
        /// Encodes all of the strokes in a strokecollection to ISF
        /// </Summary>
#else
        /// <Summary>
        /// Encodes all of the strokes in a strokecollection to ISF
        /// </Summary>
#endif
        private void StoreStrokeData(Stream localStream, GuidList guidList, ref uint cumulativeEncodedSize, ref uint localEncodedSize)
        {
            // Now we will save the stroke data
            uint currentDrawingAttributesTableIndex = 0;
            uint currentStrokeDescriptorTableIndex = 0;
            uint uCurrMetricDescriptorTableIndex = 0;
            uint currentTransformTableIndex = 0;
 
            int[] strokeIds = StrokeIdGenerator.GetStrokeIds(_coreStrokes);
            for (int i = 0; i < _coreStrokes.Count; i++)
            {
                Stroke s = _coreStrokes[i];
                uint cbStroke = 0;
 
                ISFDebugTrace("Encoding Stroke Id#" + strokeIds[i]);
 
                // if the drawing attribute index is different from the current one, write it
                if (currentDrawingAttributesTableIndex != _strokeLookupTable[s].DrawingAttributesTableIndex)
                {
                    localEncodedSize = cumulativeEncodedSize;
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.DrawingAttributesTableIndex);
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, _strokeLookupTable[s].DrawingAttributesTableIndex);
                    currentDrawingAttributesTableIndex = _strokeLookupTable[s].DrawingAttributesTableIndex;
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("    Encoded DrawingAttribute Table Index: size=" + localEncodedSize);
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // if the stroke descriptor index is different from the current one, write it
                if (currentStrokeDescriptorTableIndex != _strokeLookupTable[s].StrokeDescriptorTableIndex)
                {
                    localEncodedSize = cumulativeEncodedSize;
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.StrokeDescriptorTableIndex);
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, _strokeLookupTable[s].StrokeDescriptorTableIndex);
                    currentStrokeDescriptorTableIndex = _strokeLookupTable[s].StrokeDescriptorTableIndex;
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("    Encoded Stroke Descriptor Index: size=" + localEncodedSize);
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // if the metric table index is different from the current one, write it
                if (uCurrMetricDescriptorTableIndex != _strokeLookupTable[s].MetricDescriptorTableIndex)
                {
                    localEncodedSize = cumulativeEncodedSize;
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.MetricTableIndex);
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, _strokeLookupTable[s].MetricDescriptorTableIndex);
                    uCurrMetricDescriptorTableIndex = _strokeLookupTable[s].MetricDescriptorTableIndex;
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("    Encoded Metric Index: size=" + localEncodedSize);
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // if the Transform index is different from the current one, write it
                if (currentTransformTableIndex != _strokeLookupTable[s].TransformTableIndex)
                {
                    localEncodedSize = cumulativeEncodedSize;
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.TransformTableIndex);
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, _strokeLookupTable[s].TransformTableIndex);
                    currentTransformTableIndex = _strokeLookupTable[s].TransformTableIndex;
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("    Encoded Transform Index: size=" + localEncodedSize);
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
 
                // now create a separate Memory Stream object which will be used for storing the saved stroke data temporarily
                using (MemoryStream tempstrm = new MemoryStream(s.StylusPoints.Count * 5)) //good approximation based on profiling isf files
                {
                    localEncodedSize = cumulativeEncodedSize;
#if OLD_ISF
                    // Now save the stroke in the temp stream
                    cbStroke = StrokeSerializer.EncodeStroke(s, tempstrm, null/*we never use CompressionMode.Max)*/, GetCompressionAlgorithm(), guidList, _strokeLookupTable[s]);
#else
                    cbStroke = StrokeSerializer.EncodeStroke(s, tempstrm, GetCompressionAlgorithm(), guidList, _strokeLookupTable[s]);
#endif
 
                    if (cbStroke != tempstrm.Length)
                    {
                        throw new InvalidOperationException(ISFDebugMessage("Encoded stroke size != reported size"));
                    }
 
                    // Now write the tag KnownTagCache.KnownTagIndex.Stroke
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, (uint)KnownTagCache.KnownTagIndex.Stroke);
                    ISFDebugTrace("Stroke size=" + tempstrm.Length);
 
                    // Now write the size of the stroke
                    cumulativeEncodedSize += SerializationHelper.Encode(localStream, cbStroke);
 
                    // Finally write the stroke data
                    localStream.Write(tempstrm.GetBuffer(), 0, (int)cbStroke);
                    cumulativeEncodedSize += cbStroke;
 
                    localEncodedSize = cumulativeEncodedSize - localEncodedSize;
                    if (localEncodedSize != 0)
                        ISFDebugTrace("Encoding Stroke Id#" + strokeIds[i] + " size=" + localEncodedSize);
                    if (cumulativeEncodedSize != localStream.Length)
                        throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
                }
                if (cumulativeEncodedSize != localStream.Length)
                    throw new InvalidOperationException(ISFDebugMessage("Calculated ISF stream size != actual stream size"));
            }
        }
#if OLD_ISF
        /// <summary>
        /// Saves the stroke Ids in the stream.
        /// </summary>
        /// <param name="strokes"></param>
        /// <param name="strm"></param>
        /// <param name="forceSave">save ids even if they are contiguous</param>
        /// <returns></returns>
#else
        /// <summary>
        /// Saves the stroke Ids in the stream.
        /// </summary>
        /// <param name="strokes"></param>
        /// <param name="strm"></param>
        /// <param name="forceSave">save ids even if they are contiguous</param>
#endif
        internal static uint SaveStrokeIds(StrokeCollection strokes, Stream strm, bool forceSave)
        {
            if (0 == strokes.Count)
                return 0;
 
            // Define an ArrayList to store the stroke ids
            int[] strkIds = StrokeIdGenerator.GetStrokeIds(strokes);
 
            // First enumerate all strokes to collect the ids and also check if the follow the default sequence.
            // If they do we don't save the stroke ids
            bool fDefIds = true;
 
            if (!forceSave)
            {
                // since the stroke allocation algorithm is i++, we check if any
                //  values are not equal to the sequential and consecutive list
                for (int i = 0; i < strkIds.Length; i++)
                {
                    if (strkIds[i] != (i + 1))
                    {
                            // if non-sequential or non-consecutive, then persist the ids
                        fDefIds = false;
                        break;
                    }
                }
                // no need to store them if all of them follow the default sequence
                if (fDefIds) return 0;
            }
 
            // The format is as follows
            // <Tag.StrokeIds> <Encoded Size of Stroke Id data> <StrokeId Count> <Huff compressed array of longs>
            // Encode size of stroke count
            // First write the KnownTagCache.KnownTagIndex.StrokeIds
            uint cbWrote = SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.StrokeIds);
 
            ISFDebugTrace("Saved KnownTagCache.KnownTagIndex.StrokeIds size=" + cbWrote.ToString());
 
            // First findout the no of bytes required to huffman compress these ids
            byte algorithm = AlgoModule.DefaultCompression;
#if OLD_ISF
            byte[] data = Compressor.CompressPacketData(null, strkIds, ref algorithm);
#else
            byte[] data = Compressor.CompressPacketData(strkIds, ref algorithm);
#endif
 
 
            if (data != null)
            {
                // First write the encoded size of the buffer
                cbWrote += SerializationHelper.Encode(strm, (uint)(data.Length + SerializationHelper.VarSize((uint)strokes.Count)));
 
                // Write the count of ids
                cbWrote += SerializationHelper.Encode(strm, (uint)strokes.Count);
                strm.Write(data, 0, (int)data.Length);
                cbWrote += (uint)data.Length;
            }
            // If compression fails for some reason, write the uncompressed data
            else
            {
                byte bCompAlgo = AlgoModule.NoCompression;
 
                // Find out the size of the data + size of the id count
                uint cbStrokeId = (uint)(strokes.Count * Native.SizeOfInt + 1 + SerializationHelper.VarSize((uint)strokes.Count)); // 1 is for the compression header
 
                cbWrote += SerializationHelper.Encode(strm, cbStrokeId);
                cbWrote += SerializationHelper.Encode(strm, (uint)strokes.Count);
                strm.WriteByte(bCompAlgo);
                cbWrote++;
 
                // Now write all the ids in the stream
                // samgeo - Presharp issue
                // 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(strm);
 
                for (int i = 0; i < strkIds.Length; i++)
                {
                    bw.Write(strkIds[i]);
                    cbWrote += Native.SizeOfInt;
                }
#pragma warning restore 6518
#pragma warning restore 1634, 1691
            }
 
            return cbWrote;
        }
 
 
        #endregion
 
        #region Private Methods
 
        /// <summary>
        /// Simple helper method to examine the first 7 members (if they exist)
        /// of the byte[] and see if they have the ascii characters 'base64:' in them.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private bool IsBase64Data(Stream data)
        {
            Debug.Assert(data != null);
            long currentPosition = data.Position;
            try
            {
                if (data.Length < Base64HeaderBytes.Length)
                {
                    return false;
                }
 
                for (int x = 0; x < Base64HeaderBytes.Length; x++)
                {
                    if ((byte)data.ReadByte() != Base64HeaderBytes[x])
                    {
                        return false;
                    }
                }
                return true;
            }
            finally
            {
                //reset position
                data.Position = currentPosition;
            }
        }
 
        /// <summary>
        /// Builds the GuidList based on ExtendedPropeties and StrokeCollection
        /// </summary>
        /// <returns></returns>
        private GuidList BuildGuidList()
        {
            GuidList guidList = new GuidList();
            int i = 0;
 
            // First go through the list of ink properties
            ExtendedPropertyCollection attributes = _coreStrokes.ExtendedProperties;
            for (i = 0; i < attributes.Count; i++)
            {
                guidList.Add(attributes[i].Id);
            }
 
            // Next go through all the strokes
            for (int j = 0; j < _coreStrokes.Count; j++)
            {
                BuildStrokeGuidList(_coreStrokes[j], guidList);
            }
 
            return guidList;
        }
        /// <summary>
        /// Builds the list of Custom Guids that were used by this particular stroke, either in the packet layout
        /// or in the drawing attributes, or in the buttons or in Extended properties or in the point properties
        /// and updates the guidlist with that information
        /// </summary>
        /// <param name="stroke"></param>
        /// <param name="guidList"></param>
        private void BuildStrokeGuidList(Stroke stroke, GuidList guidList)
        {
            int i = 0;
 
            // First drawing attributes
            //      Ignore the default Guids/attributes in the DrawingAttributes
            int count;
            Guid[] guids = ExtendedPropertySerializer.GetUnknownGuids(stroke.DrawingAttributes.ExtendedProperties, out count);
 
            for (i = 0; i < count; i++)
            {
                guidList.Add(guids[i]);
            }
 
            Guid[] descriptionGuids = stroke.StylusPoints.Description.GetStylusPointPropertyIds();
            for (i = 0; i < descriptionGuids.Length; i++)
            {
                guidList.Add(descriptionGuids[i]);
            }
 
            if (stroke.ExtendedProperties.Count > 0)
            {
                // Add the ExtendedProperty guids in the list
                for (i = 0; i < stroke.ExtendedProperties.Count; i++)
                {
                    guidList.Add(stroke.ExtendedProperties[i].Id);
                }
            }
        }
 
 
        private byte GetCompressionAlgorithm()
        {
            if (CompressionMode.Compressed == CurrentCompressionMode)
            {
                return AlgoModule.DefaultCompression;
            }
            return AlgoModule.NoCompression;
        }
 
 
        /// <summary>
        /// This function serializes Stroke Descriptor Table in the stream. For information on how they are serialized, please refer to the spec.
        ///
        /// </summary>
        /// <param name="strm"></param>
        /// <returns></returns>
        private uint SerializePacketDescrTable(Stream strm)
        {
            if (_strokeDescriptorTable.Count == 0)
                return 0;
 
            int count = 0;
            uint cbData = 0;
 
            // First add the appropriate header information
            if (_strokeDescriptorTable.Count == 1)
            {
                StrokeDescriptor tmp = _strokeDescriptorTable[0];
 
                // If there is no tag, that means default template and only one entry in the list. Return from here
                if (tmp.Template.Count == 0)
                    return 0;
                else
                {
                    // Write it out directly
                    // First the tag
                    cbData += SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.StrokeDescriptorBlock);
 
                    // Now encode the descriptor itself
                    cbData += EncodeStrokeDescriptor(strm, tmp);
                }
            }
            else
            {
                uint cbTotal = 0;
 
                // First calculate the total encoded size of the all the Templates
                for (count = 0; count < _strokeDescriptorTable.Count; count++)
                {
                    cbTotal += SerializationHelper.VarSize((_strokeDescriptorTable[count]).Size) + (_strokeDescriptorTable[count]).Size;
                }
 
                // Now write the Tag
                cbData += SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.StrokeDescriptorTable);
                cbData += SerializationHelper.Encode(strm, cbTotal);
 
                // Now write the encoded templates
                for (count = 0; count < _strokeDescriptorTable.Count; count++)
                {
                    cbData += EncodeStrokeDescriptor(strm, _strokeDescriptorTable[count]);
                }
            }
 
            return cbData;
        }
 
 
        /// <summary>
        /// This function serializes Metric Descriptor Table in the stream. For information on how they are serialized, please refer to the spec.
        /// </summary>
        /// <param name="strm"></param>
        /// <returns></returns>
        private uint SerializeMetricTable(Stream strm)
        {
            uint cSize = 0;
            MetricBlock block;
 
            if (0 == _metricTable.Count)
                return 0;
 
            for (int i = 0; i < _metricTable.Count; i++)
                cSize += _metricTable[i].Size;
 
            uint cbData = 0;
 
            // if total size of the blocks is 1, then there is nothing to write
            //  the reason that the size of the blocks is 1 instead of 0 is because
            //  MetricBlock.Size returns the size of the block plus the byte encoded
            //  size value itself. If the MetricBlock size value is 0, then byte
            //  encoded size value is 0, which has a byte size of 1.
            if (1 == cSize)
            {
                return 0;
            }
            else if (1 == _metricTable.Count)
            {
                cbData += SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.MetricBlock);
            }
            else
            {
                cbData += SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.MetricTable);
                cbData += SerializationHelper.Encode(strm, cSize);
            }
 
            for (int i = 0; i < _metricTable.Count; i++)
            {
                block = _metricTable[i];
                cbData += block.Pack(strm);
            }
 
            return cbData;
        }
 
 
        /// <summary>
        /// Multibyte Encodes a Stroke Descroptor
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="strd"></param>
        /// <returns></returns>
        private uint EncodeStrokeDescriptor(Stream strm, StrokeDescriptor strd)
        {
            uint cbData = 0;
 
            // First encode the size of the descriptor
            cbData += SerializationHelper.Encode(strm, strd.Size);
            for (int count = 0; count < strd.Template.Count; count++)
            {
                // Now encode all members of the descriptor
                cbData += SerializationHelper.Encode(strm, (uint)strd.Template[count]);
            }
 
            return cbData;
        }
 
 
        /// <summary>
        /// This function serializes Transform Descriptor Table in the stream. For information on how they are serialized, please refer to the spec.
        /// </summary>
        /// <param name="strm"></param>
        /// <returns></returns>
        private uint SerializeTransformTable(Stream strm)
        {
            // If there is only one entry in the TransformDescriptor table
            //      and it is the default descriptor, skip serialization of transforms
            if (_transformTable.Count == 1 && _transformTable[0].Size == 0)
            {
                return 0;
            }
 
            uint floatTotal = 0;
            uint doubleTotal = 0;
 
            // First count the size of all transforms (handling both float && double versions)
            for (int i = 0; i < _transformTable.Count; i++)
            {
                TransformDescriptor xform = _transformTable[i];
                uint cbLocal = SerializationHelper.VarSize((uint)xform.Tag);
 
                floatTotal += cbLocal;
                doubleTotal += cbLocal;
                if (KnownTagCache.KnownTagIndex.TransformRotate == xform.Tag)
                {
                    cbLocal = SerializationHelper.VarSize((uint)(xform.Transform[0] + 0.5f));
                    floatTotal += cbLocal;
                    doubleTotal += cbLocal;
                }
                else
                {
                    cbLocal = xform.Size * Native.SizeOfFloat;
                    floatTotal += cbLocal;
                    doubleTotal += cbLocal * 2;
                }
            }
 
            uint cbTotal = 0;
 
            // If there is only one entry in the TransformDescriptor table
            if (_transformTable.Count == 1)
            {
                TransformDescriptor xform = _transformTable[0];
 
                cbTotal = EncodeTransformDescriptor(strm, xform, false);
            }
            else
            {
                // Now first write the block descriptor and then write all transforms
                cbTotal += SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.TransformTable);
                cbTotal += SerializationHelper.Encode(strm, floatTotal);
                for (int i = 0; i < _transformTable.Count; i++)
                {
                    cbTotal += EncodeTransformDescriptor(strm, _transformTable[i], false);
                }
            }
            // now write the Extended Transform table (using doubles instead of floats)
            { // note that we do not distinguish between 1 and > 1 transforms for compression
                // Now first write the block descriptor and then write all transforms
                cbTotal += SerializationHelper.Encode(strm, (uint)KnownTagCache.KnownTagIndex.ExtendedTransformTable);
                cbTotal += SerializationHelper.Encode(strm, doubleTotal);
                for (int i = 0; i < _transformTable.Count; i++)
                {
                    cbTotal += EncodeTransformDescriptor(strm, _transformTable[i], true);
                }
            }
            return cbTotal;
        }
 
 
        /// <summary>
        /// Multibyte Encode if necessary a Transform Descriptor into the stream
        /// </summary>
        /// <param name="strm"></param>
        /// <param name="xform"></param>
        /// <param name="useDoubles"></param>
        /// <returns></returns>
        private uint EncodeTransformDescriptor(Stream strm, TransformDescriptor xform, bool useDoubles)
        {
            uint cbData = 0;
 
            // First encode the tag
            cbData = SerializationHelper.Encode(strm, (uint)xform.Tag);
 
            // Encode specially if transform denotes rotation
            if (KnownTagCache.KnownTagIndex.TransformRotate == xform.Tag)
            {
                uint angle = (uint)(xform.Transform[0] + 0.5f);
 
                cbData += SerializationHelper.Encode(strm, angle);
            }
            else
            {
                // samgeo - Presharp issue
                // 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(strm);
 
                for (int i = 0; i < xform.Size; i++)
                {
                    // note that the binary writer changes serialization
                    //      lengths depending on the Write parameter cast
                    if (useDoubles)
                    {
                        bw.Write(xform.Transform[i]);
                        cbData += Native.SizeOfDouble;
                    }
                    else
                    {
                        bw.Write((float)xform.Transform[i]);
                        cbData += Native.SizeOfFloat;
                    }
                }
#pragma warning restore 6518
#pragma warning restore 1634, 1691
            }
 
            return cbData;
        }
 
#if OLD_ISF
        /// <summary>
        /// This function serializes Drawing Attributes Table in the stream. For information on how they are serialized, please refer to the spec.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="guidList"></param>
        /// <returns></returns>
#else
        /// <summary>
        /// This function serializes Drawing Attributes Table in the stream. For information on how they are serialized, please refer to the spec.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="guidList"></param>
#endif
        private uint SerializeDrawingAttrsTable(Stream stream, GuidList guidList)
        {
            uint totalSizeOfSerializedBytes = 0;
            uint sizeOfHeaderInBytes = 0;
 
            if (1 == _drawingAttributesTable.Count)
            {
                //we always serialize a single DA, even if it has default values so we will write width back to the stream
                DrawingAttributes drawingAttributes = _drawingAttributesTable[0];
 
                // There is single drawing attribute. Save it along with the size
                totalSizeOfSerializedBytes += SerializationHelper.Encode(stream, (uint)KnownTagCache.KnownTagIndex.DrawingAttributesBlock);
 
                // Get the size of the saved bytes
                using (MemoryStream drawingAttributeStream = new MemoryStream(16)) //reasonable default based onn profiling
                {
                    sizeOfHeaderInBytes = DrawingAttributeSerializer.EncodeAsISF(drawingAttributes, drawingAttributeStream, guidList, 0, true);
 
                    // Write the size first
                    totalSizeOfSerializedBytes += SerializationHelper.Encode(stream, sizeOfHeaderInBytes);
 
                    // write the data
                    uint bytesWritten = Convert.ToUInt32(drawingAttributeStream.Position);
                    totalSizeOfSerializedBytes += bytesWritten;
                    Debug.Assert(sizeOfHeaderInBytes == bytesWritten);
 
                    stream.Write(   drawingAttributeStream.GetBuffer(), //returns a direct ref, no copied
                                    0,
                                    Convert.ToInt32(bytesWritten));
 
                    drawingAttributeStream.Dispose();
                }
            }
            else
            {
                // Temporarily declare an array to hold the size of the saved drawing attributes
                uint[] sizes = new uint[_drawingAttributesTable.Count];
                MemoryStream[] drawingAttributeStreams = new MemoryStream[_drawingAttributesTable.Count];
 
                // First calculate the size of each attribute
                for (int i = 0; i < _drawingAttributesTable.Count; i++)
                {
                    DrawingAttributes drawingAttributes = _drawingAttributesTable[i];
                    drawingAttributeStreams[i] = new MemoryStream(16); //reasonable default based on profiling
 
                    sizes[i] = DrawingAttributeSerializer.EncodeAsISF(drawingAttributes, drawingAttributeStreams[i], guidList, 0, true);
                    sizeOfHeaderInBytes += SerializationHelper.VarSize(sizes[i]) + sizes[i];
                }
 
                // Now write the KnownTagCache.KnownTagIndex.DrawingAttributesTable first, then sizeOfHeaderInBytes and then individual Drawing Attributes
                totalSizeOfSerializedBytes = SerializationHelper.Encode(stream, (uint)KnownTagCache.KnownTagIndex.DrawingAttributesTable);
 
                totalSizeOfSerializedBytes += SerializationHelper.Encode(stream, sizeOfHeaderInBytes);
                for (int i = 0; i < _drawingAttributesTable.Count; i++)
                {
                    DrawingAttributes drawingAttributes = _drawingAttributesTable[i];
 
                    // write the size of the block
                    totalSizeOfSerializedBytes += SerializationHelper.Encode(stream, sizes[i]);
 
                    // write the saved data
                    uint bytesWritten = Convert.ToUInt32(drawingAttributeStreams[i].Position);
                    totalSizeOfSerializedBytes += bytesWritten;
                    Debug.Assert(sizes[i] == bytesWritten);
 
                    stream.Write(   drawingAttributeStreams[i].GetBuffer(), //returns a direct ref, no copies
                                    0,
                                    Convert.ToInt32(bytesWritten));
 
                    drawingAttributeStreams[i].Dispose();
                }
            }
 
            return totalSizeOfSerializedBytes;
        }
 
        /// <summary>
        /// This function builds list of all unique Tables, ie Stroke Descriptor Table, Metric Descriptor Table, Transform Descriptor Table
        /// and Drawing Attributes Table based on all the strokes. Each entry in the Table is unique with respect to the table.
        /// </summary>
        /// <param name="guidList"></param>
        private void BuildTables(GuidList guidList)
        {
            _transformTable.Clear();
            _strokeDescriptorTable.Clear();
            _metricTable.Clear();
            _drawingAttributesTable.Clear();
 
            int count = 0;
 
            for (count = 0; count < _coreStrokes.Count; count++)
            {
                Stroke stroke = _coreStrokes[count];
 
                // First get the updated descriptor from the stroke
                StrokeDescriptor strokeDescriptor;
                MetricBlock metricBlock;
                StrokeSerializer.BuildStrokeDescriptor(stroke, guidList, _strokeLookupTable[stroke], out strokeDescriptor, out metricBlock);
                bool fMatch = false;
 
                // Compare this with all the global stroke descriptor for a match
                for (int descriptorIndex = 0; descriptorIndex < _strokeDescriptorTable.Count; descriptorIndex++)
                {
                    if (strokeDescriptor.IsEqual(_strokeDescriptorTable[descriptorIndex]))
                    {
                        fMatch = true;
                        _strokeLookupTable[stroke].StrokeDescriptorTableIndex = (uint)descriptorIndex;
                        break;
                    }
                }
                if (false == fMatch)
                {
                    _strokeDescriptorTable.Add(strokeDescriptor);
                    _strokeLookupTable[stroke].StrokeDescriptorTableIndex = (uint)_strokeDescriptorTable.Count - 1;
                }
 
                // If there is at least one entry in the metric block, check if the current Block is equvalent to
                // any of the existing one.
                fMatch = false;
                for (int tmp = 0; tmp < _metricTable.Count; tmp++)
                {
                    MetricBlock block = _metricTable[tmp];
                    SetType type = SetType.SubSet;
 
                    if (block.CompareMetricBlock(metricBlock, ref type))
                    {
                        // This entry exists in the list. If it is a subset of the element, do nothing.
                        // Otherwise, replace the entry with this one
                        if (type == SetType.SuperSet)
                        {
                            _metricTable[tmp] = metricBlock;
                        }
 
                        fMatch = true;
                        _strokeLookupTable[stroke].MetricDescriptorTableIndex = (uint)tmp;
                        break;
                    }
                }
 
                if (false == fMatch)
                {
                    _metricTable.Add(metricBlock);
                    _strokeLookupTable[stroke].MetricDescriptorTableIndex = (uint)(_metricTable.Count - 1);
                }
 
                // Now build the Transform Table
                fMatch = false;
 
                //
                // always identity
                //
                TransformDescriptor xform = StrokeCollectionSerializer.IdentityTransformDescriptor;
 
                // First check to see if this matches with any existing Transform Blocks
                for (int i = 0; i < _transformTable.Count; i++)
                {
                    if (true == xform.Compare(_transformTable[i]))
                    {
                        fMatch = true;
                        _strokeLookupTable[stroke].TransformTableIndex = (uint)i;
                        break;
                    }
                }
 
                if (false == fMatch)
                {
                    _transformTable.Add(xform);
                    _strokeLookupTable[stroke].TransformTableIndex = (uint)(_transformTable.Count - 1);
                }
 
                // Now build the drawing attributes table
                fMatch = false;
 
                DrawingAttributes drattrs = _coreStrokes[count].DrawingAttributes;
 
                // First check to see if this matches with any existing transform blocks
                for (int i = 0; i < _drawingAttributesTable.Count; i++)
                {
                    if (true == drattrs.Equals(_drawingAttributesTable[i]))
                    {
                        fMatch = true;
                        _strokeLookupTable[stroke].DrawingAttributesTableIndex = (uint)i;
                        break;
                    }
                }
 
                if (false == fMatch)
                {
                    _drawingAttributesTable.Add(drattrs);
                    _strokeLookupTable[stroke].DrawingAttributesTableIndex = (uint)_drawingAttributesTable.Count - 1;
                }
            }
        }
 
        #endregion // Private Methods
 
        internal class StrokeLookupEntry
        {
            internal uint MetricDescriptorTableIndex = 0;
            internal uint StrokeDescriptorTableIndex = 0;
            internal uint TransformTableIndex = 0;
            internal uint DrawingAttributesTableIndex = 0;
 
            // Compression algorithm data
            internal byte CompressionData = 0;
 
            internal int[][] ISFReadyStrokeData = null;
            internal bool StorePressure = false;
        }
 
        #endregion // Encoding
 
        #region Debugging Methods
 
        [System.Diagnostics.Conditional("DEBUG_ISF")]
        static void ISFDebugTrace(string message)
        {
            System.Diagnostics.Debug.WriteLine(message);
        }
        #endregion
 
        // [System.Diagnostics.Conditional("DEBUG_ISF")]
        internal static string ISFDebugMessage(string debugMessage)
        {
#if DEBUG
            return debugMessage;
#else
            return SR.IsfOperationFailed;
#endif
        }
 
        #region Private Fields
 
        StrokeCollection _coreStrokes;
        private System.Collections.Generic.List<StrokeDescriptor> _strokeDescriptorTable = null;
        private System.Collections.Generic.List<TransformDescriptor> _transformTable = null;
        private System.Collections.Generic.List<DrawingAttributes> _drawingAttributesTable = null;
        private System.Collections.Generic.List<MetricBlock> _metricTable = null;
        private Vector _himetricSize = new Vector(0.0f, 0.0f);
 
 
            // The ink space rectangle (e.g. bounding box for GIF) is stored
            //      with the serialization info so that load/save roundtrip the
            //      rectangle
        private Rect _inkSpaceRectangle = new Rect();
 
        System.Collections.Generic.Dictionary<Stroke, StrokeLookupEntry> _strokeLookupTable = null;
 
        #endregion
    }
 
    /// <summary>
    /// Simple static method for generating StrokeIds
    /// </summary>
    internal static class StrokeIdGenerator
    {
        /// <summary>
        /// Generates backwards compatible StrokeID's for the strokes
        /// </summary>
        /// <param name="strokes">strokes</param>
        /// <returns></returns>
        internal static int[] GetStrokeIds(StrokeCollection strokes)
        {
            System.Diagnostics.Debug.Assert(strokes != null);
 
            int[] strokeIds = new int[strokes.Count];
            for (int x = 0; x < strokeIds.Length; x++)
            {
                //stroke ID's are 1 based (1,2,3...)
                strokeIds[x] = x + 1;
            }
            return strokeIds;
        }
    }
}