File: MS\Internal\Ink\InkSerializedFormat\StrokeSerializer.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 System.IO;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Ink;
 
namespace MS.Internal.Ink.InkSerializedFormat
{
    internal static class StrokeSerializer
    {
        #region Decoding
 
        #region Public Methods
#if OLD_ISF
        /// <summary>
        /// Loads a stroke from the stream based on Stroke Descriptor, StylusPointDescription, Drawing Attributes, Stroke IDs, transform and GuidList
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="size"></param>
        /// <param name="guidList"></param>
        /// <param name="strokeDescriptor"></param>
        /// <param name="stylusPointDescription"></param>
        /// <param name="drawingAttributes"></param>
        /// <param name="transform"></param>
        /// <param name="compressor">Compression module</param>
        /// <param name="stroke">Newly decoded stroke</param>
        /// <returns></returns>
#else
        /// <summary>
        /// Loads a stroke from the stream based on Stroke Descriptor, StylusPointDescription, Drawing Attributes, Stroke IDs, transform and GuidList
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="size"></param>
        /// <param name="guidList"></param>
        /// <param name="strokeDescriptor"></param>
        /// <param name="stylusPointDescription"></param>
        /// <param name="drawingAttributes"></param>
        /// <param name="transform"></param>
        /// <param name="stroke">Newly decoded stroke</param>
#endif
        internal static uint DecodeStroke(Stream stream,
                                 uint size,
                                 GuidList guidList,
                                 StrokeDescriptor strokeDescriptor,
                                 StylusPointDescription stylusPointDescription,
                                 DrawingAttributes drawingAttributes,
                                 Matrix transform,
#if OLD_ISF
                                 Compressor compressor,
#endif
                                 out Stroke stroke)
        {
            ExtendedPropertyCollection extendedProperties;
            StylusPointCollection stylusPoints;
 
            uint cb = DecodeISFIntoStroke(
#if OLD_ISF
                compressor, 
#endif 
                stream, 
                size, 
                guidList, 
                strokeDescriptor, 
                stylusPointDescription, 
                transform, 
                out stylusPoints, 
                out extendedProperties);
 
            if (cb != size)
            {
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Stroke size (" +
                    cb.ToString(System.Globalization.CultureInfo.InvariantCulture) + ") != expected (" + 
                    size.ToString(System.Globalization.CultureInfo.InvariantCulture) + ")"));
            }
 
            stroke = new Stroke(stylusPoints, drawingAttributes, extendedProperties);
 
            return cb;
        }
#if OLD_ISF
        /// <summary>
        /// This functions loads a stroke from a memory stream based on the descriptor and GuidList. It returns
        /// the no of bytes it has read from the stream to correctly load the stream, which should be same as
        /// the value of the size parameter. If they are unequal throws ArgumentException. Stroke descriptor is
        /// used to load the packetproperty as well as ExtendedPropertyCollection on this stroke. Compressor is used
        /// to decompress the data.
        /// </summary>
        /// <param name="compressor"></param>
        /// <param name="stream"></param>
        /// <param name="totalBytesInStrokeBlockOfIsfStream"></param>
        /// <param name="guidList"></param>
        /// <param name="strokeDescriptor"></param>
        /// <param name="stylusPointDescription"></param>
        /// <param name="transform"></param>
        /// <param name="stylusPoints"></param>
        /// <param name="extendedProperties"></param>
        /// <returns></returns>
#else
        /// <summary>
        /// This functions loads a stroke from a memory stream based on the descriptor and GuidList. It returns
        /// the no of bytes it has read from the stream to correctly load the stream, which should be same as
        /// the value of the size parameter. If they are unequal throws ArgumentException. Stroke descriptor is
        /// used to load the packetproperty as well as ExtendedPropertyCollection on this stroke. Compressor is used
        /// to decompress the data.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="totalBytesInStrokeBlockOfIsfStream"></param>
        /// <param name="guidList"></param>
        /// <param name="strokeDescriptor"></param>
        /// <param name="stylusPointDescription"></param>
        /// <param name="transform"></param>
        /// <param name="stylusPoints"></param>
        /// <param name="extendedProperties"></param>
#endif
        static uint DecodeISFIntoStroke(
#if OLD_ISF
            Compressor compressor, 
#endif
            Stream stream, 
            uint totalBytesInStrokeBlockOfIsfStream, 
            GuidList guidList, 
            StrokeDescriptor strokeDescriptor,
            StylusPointDescription stylusPointDescription,
            Matrix transform,
            out StylusPointCollection stylusPoints, 
            out ExtendedPropertyCollection extendedProperties)
        {
            stylusPoints = null;
            extendedProperties = null;
 
            // We do allow a stroke with no packet data
            if (0 == totalBytesInStrokeBlockOfIsfStream)
            {
                return 0;
            }
             
            uint locallyDecodedBytes;
            uint remainingBytesInStrokeBlock = totalBytesInStrokeBlockOfIsfStream;
 
            // First try to load any packet data
            locallyDecodedBytes = LoadPackets(  stream, 
                                                remainingBytesInStrokeBlock, 
#if OLD_ISF
                                                compressor, 
#endif
                                                stylusPointDescription, 
                                                transform, 
                                                out stylusPoints);
 
            if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Packet buffer overflowed the ISF stream"));
 
            remainingBytesInStrokeBlock -= locallyDecodedBytes;
            if (0 == remainingBytesInStrokeBlock)
            {
                return locallyDecodedBytes;
            }
 
            // Now read the extended propertes
            for (int iTag = 1; iTag < strokeDescriptor.Template.Count && remainingBytesInStrokeBlock > 0; iTag++)
            {
                KnownTagCache.KnownTagIndex tag = strokeDescriptor.Template[iTag - 1];
 
                switch (tag)
                {
                    case MS.Internal.Ink.InkSerializedFormat.KnownTagCache.KnownTagIndex.StrokePropertyList:
                        {
                            // we've found the stroke extended properties. Load them now.
                            while (iTag < strokeDescriptor.Template.Count && remainingBytesInStrokeBlock > 0)
                            {
                                tag = strokeDescriptor.Template[iTag];
 
                                object data;
                                Guid guid = guidList.FindGuid(tag);
                                if (guid == Guid.Empty)
                                {
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Stroke Custom Attribute tag embedded in ISF stream does not match guid table"));
                                }
 
                                // load the extended property data from the stream (and decode the type)
                                locallyDecodedBytes = ExtendedPropertySerializer.DecodeAsISF(stream, remainingBytesInStrokeBlock, guidList, tag, ref guid, out data);
 
                                // add the guid/data pair into the property collection (don't redecode the type)
                                if (extendedProperties == null)
                                {
                                    extendedProperties = new ExtendedPropertyCollection();
                                }
                                extendedProperties[guid] = data;
                                if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                                remainingBytesInStrokeBlock -= locallyDecodedBytes;
                                iTag++;
                            }
                        }
                        break;
 
                    case MS.Internal.Ink.InkSerializedFormat.KnownTagCache.KnownTagIndex.Buttons:
                        {
                            // Next tag is count of buttons and the tags for the button guids
                            iTag += (int)((uint)strokeDescriptor.Template[iTag]) + 1;
                        }
                        break;
 
                    // ignore any tags embedded in the Stroke block that this
                    //      version of the ISF decoder doesn't understand
                    default:
                        {
                            System.Diagnostics.Trace.WriteLine("Ignoring unhandled stroke tag in ISF stroke descriptor");
                        }
                        break;
                }
            }
 
            // Now try to load any tagged property data or point property data
            while (remainingBytesInStrokeBlock > 0)
            {
                // Read the tag first
                KnownTagCache.KnownTagIndex tag;
                uint uiTag;
 
                locallyDecodedBytes = SerializationHelper.Decode(stream, out uiTag);
                tag = (KnownTagCache.KnownTagIndex)uiTag;
                if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                remainingBytesInStrokeBlock -= locallyDecodedBytes;
 
                // if it is a point property block
                switch (tag)
                {
                    case MS.Internal.Ink.InkSerializedFormat.KnownTagCache.KnownTagIndex.PointProperty:
                        {
                            // First load the totalBytesInStrokeBlockOfIsfStream of the point property block
                            uint cbsize;
 
                            locallyDecodedBytes = SerializationHelper.Decode(stream, out cbsize);
                            if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                            remainingBytesInStrokeBlock -= locallyDecodedBytes;
                            while (remainingBytesInStrokeBlock > 0)
                            {
                                // First read the tag corresponding to the property
                                locallyDecodedBytes = SerializationHelper.Decode(stream, out uiTag);
                                tag = (KnownTagCache.KnownTagIndex)uiTag;
                                if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                                remainingBytesInStrokeBlock -= locallyDecodedBytes;
 
                                // Now read the packet index for which the property will apply
                                uint propindex;
 
                                locallyDecodedBytes = SerializationHelper.Decode(stream, out propindex);
                                if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                                remainingBytesInStrokeBlock -= locallyDecodedBytes;
 
                                uint propsize;
 
                                locallyDecodedBytes = SerializationHelper.Decode(stream, out propsize);
                                if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                                remainingBytesInStrokeBlock -= locallyDecodedBytes;
 
                                // Compressed data totalBytesInStrokeBlockOfIsfStream
                                propsize += 1;
 
                                // Make sure we have enough data to read
                                if (propsize > remainingBytesInStrokeBlock)
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                                byte[] in_buffer = new byte[propsize];
 
                                uint bytesRead = StrokeCollectionSerializer.ReliableRead(stream, in_buffer, propsize);
                                if (propsize != bytesRead)
                                {
                                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Read different size from stream then expected"));
                                }
 
                                byte[] out_buffer = Compressor.DecompressPropertyData(in_buffer);
 
                                System.Diagnostics.Debug.Assert(false, "ExtendedProperties for points are not supported");
 
                                // skip the bytes in both success & failure cases
                                // Note: Point ExtendedProperties are discarded
                                remainingBytesInStrokeBlock -= propsize;
                            }
                        }
                        break;
 
                    default:
                        {
                            object data;
                            Guid guid = guidList.FindGuid(tag);
                            if (guid == Guid.Empty)
                            {
                                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Stroke Custom Attribute tag embedded in ISF stream does not match guid table"));
                            }
 
                            // load the extended property data from the stream (and decode the type)
                            locallyDecodedBytes = ExtendedPropertySerializer.DecodeAsISF(stream, remainingBytesInStrokeBlock, guidList, tag, ref guid, out data);
 
                            // add the guid/data pair into the property collection (don't redecode the type)
                            if (extendedProperties == null)
                            {
                                extendedProperties = new ExtendedPropertyCollection();
                            }
                            extendedProperties[guid] = data;
                            if (locallyDecodedBytes > remainingBytesInStrokeBlock)
                            {
                                throw new InvalidOperationException(StrokeCollectionSerializer.ISFDebugMessage("ExtendedProperty decoded totalBytesInStrokeBlockOfIsfStream exceeded ISF stream totalBytesInStrokeBlockOfIsfStream"));
                            }
 
                            remainingBytesInStrokeBlock -= locallyDecodedBytes;
                        }
                        break;
                }
            }
 
            if (0 != remainingBytesInStrokeBlock)
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
            return totalBytesInStrokeBlockOfIsfStream;
        }
#if OLD_ISF
        /// <summary>
        /// Loads packets from the input stream.  For example, packets are all of the x's in a stroke
        /// </summary>
#else
        /// <summary>
        /// Loads packets from the input stream.  For example, packets are all of the x's in a stroke
        /// </summary>
#endif
        static uint LoadPackets(Stream inputStream, 
                                uint totalBytesInStrokeBlockOfIsfStream, 
#if OLD_ISF
                                Compressor compressor, 
#endif
                                StylusPointDescription stylusPointDescription, 
                                Matrix transform, 
                                out StylusPointCollection stylusPoints)
        {
            stylusPoints = null;
 
            if (0 == totalBytesInStrokeBlockOfIsfStream)
                return 0;
 
            uint locallyDecodedBytesRemaining = totalBytesInStrokeBlockOfIsfStream;
            uint localBytesRead;
 
            // First read the no of packets
            uint pointCount;
 
            localBytesRead = SerializationHelper.Decode(inputStream, out pointCount);
            if (locallyDecodedBytesRemaining < localBytesRead)
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
            locallyDecodedBytesRemaining -= localBytesRead;
            if (0 == locallyDecodedBytesRemaining)
                return localBytesRead;
 
            // Allocate packet properties
            int intsPerPoint = stylusPointDescription.GetInputArrayLengthPerPoint();
            int buttonCount = stylusPointDescription.ButtonCount;
            int buttonIntsPerPoint = (buttonCount > 0 ? 1 : 0);
            int valueIntsPerPoint = intsPerPoint - buttonIntsPerPoint;
            //add one int per point for button data if it exists
            int[] rawPointData = new int[pointCount * intsPerPoint];
            int[] packetDataSet = new int[pointCount];
            
 
            // copy the rest of the data from the stroke data
            byte[] inputBuffer = new byte[locallyDecodedBytesRemaining];
 
            // Read the input data into the byte array
            uint bytesRead = StrokeCollectionSerializer.ReliableRead(inputStream, inputBuffer, locallyDecodedBytesRemaining);
 
            if ( bytesRead != locallyDecodedBytesRemaining )
            {
                // Make sure the bytes read are expected. If not, we should bail out.
                // An exception will be thrown.
                throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
            }
 
            // at this point, we have read all of the bytes remaining in the input
            //      stream's packet block, and while we will keep the bytes remaining
            //      variable for positioning within the local byte buffer, we should
            //      not read from the stream again, or we risk reading into another
            //      ISF tag's block.
            int originalPressureIndex = stylusPointDescription.OriginalPressureIndex;
            for (int i = 0; i < valueIntsPerPoint && locallyDecodedBytesRemaining > 0; i++)
            {
                localBytesRead = locallyDecodedBytesRemaining;
                Compressor.DecompressPacketData(
#if OLD_ISF
                        compressor, 
#endif
                        inputBuffer, 
                        ref localBytesRead, 
                        packetDataSet);
 
                if (localBytesRead > locallyDecodedBytesRemaining)
                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid ISF data"));
 
                //
                // packetDataSet is like this:
                // -------------
                // |X|X|X|X|X|X|
                // -------------
                //
                // we need to copy into rawPointData at
                //
                // -------------
                // |X| |X| |X| |
                // -------------
                //
                // additionally, for NormalPressure, if it exists and was 
                // reordered in the StylusPointDescription, we need to account for that here
                //
                int tempi = i;
                if (tempi > 1 && 
                    originalPressureIndex != -1 && 
                    originalPressureIndex != StylusPointDescription.RequiredPressureIndex/*2*/)
                {
                    //
                    // NormalPressure exists in the packet stream and was not at index 2
                    // StylusPointDescription enforces that NormalPressure is at index 2
                    // so we need to copy packet data beyond X and Y into a different location
                    //
                    // take the example of the original StylusPointDescription
                    //  |X|Y|XTilt|YTilt|NormalPressure|Rotation|
                    //
                    // originalPressureIndex is 4, and we know it is now 2
                    // which means that everything before index 4 has been shifted one
                    // and everything after index 4 is still good.  Index 4 should be copied to index 2
                    if (tempi == originalPressureIndex)
                    {
                        tempi = 2;
                    }
                    else if (tempi < originalPressureIndex)
                    {
                        tempi++;
                    }
                }
 
                locallyDecodedBytesRemaining -= localBytesRead;
                for (int j = 0, x = 0; j < pointCount; j++, x += intsPerPoint)
                {
                    rawPointData[x + tempi] = packetDataSet[j];
                }
 
                // Move the array elements to point to next set of compressed data
                for (uint u = 0; u < locallyDecodedBytesRemaining; u++)
                {
                    inputBuffer[u] = inputBuffer[u + (int)localBytesRead];
                }
            }
 
            // Now that we've read packet data, we must read button data if it is there
            byte[] buttonData = null;
            // since the button state is a simple bit value (either down or up), the button state
            // for a series of packets is packed into an array of bits rather than integers
            // For example, if there are 16 packets, and 2 buttons, then 32 bits can be used (e.g. 1 32-bit integer)
            if (0 != locallyDecodedBytesRemaining && buttonCount > 0)
            {
                    // calculate the number of full bytes used for buttons per packet
                    //   Example: 10 buttons would require 1 full byte
                int fullBytesForButtonsPerPacket = buttonCount / Native.BitsPerByte;
                    // calculate the number of bits that spill beyond the full byte boundary
                    //   Example: 10 buttons would require 2 extra bits (8 fit in a full byte)
                int partialBitsForButtonsPerPacket = buttonCount % Native.BitsPerByte;
 
                // Now figure out how many bytes we need to read for the button data
                localBytesRead = (uint)((buttonCount * pointCount + 7) / Native.BitsPerByte);
                if (localBytesRead > locallyDecodedBytesRemaining)
                {
                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Buffer range is smaller than expected expected size"));
                }
                locallyDecodedBytesRemaining -= localBytesRead;
 
                int buttonSizeInBytes = (buttonCount + 7)/Native.BitsPerByte;
                buttonData = new byte[pointCount * buttonSizeInBytes];
 
                // Create a bit reader to unpack the bits from the ISF stream into
                //    loosely packed byte buffer (e.g. button data aligned on full byte
                //    boundaries only)
                BitStreamReader bitReader =
                    new BitStreamReader(inputBuffer, (uint)buttonCount * pointCount);
 
                // unpack the button data into each packet
                int byteCounter = 0;
                while (!bitReader.EndOfStream)
                {
                    // unpack the fully bytes first
                    for (int fullBytes = 0; fullBytes < fullBytesForButtonsPerPacket; fullBytes++)
                    {
                        buttonData[byteCounter++] = bitReader.ReadByte(Native.BitsPerByte);
                    }
                    // then unpack a single partial byte if necessary
                    if (partialBitsForButtonsPerPacket > 0)
                    {
                        buttonData[byteCounter++] = bitReader.ReadByte((int)partialBitsForButtonsPerPacket);
                    }
                }
 
                // if the number of bytes allocated != necessary byte amount then an error occurred
                if (byteCounter != buttonData.Length)
                {
                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Button data length not equal to expected length"));
                }
 
                //
                // set the point data in the raw array
                //
                FillButtonData( (int)pointCount,
                                buttonCount,
                                valueIntsPerPoint, //gives the first button index
                                rawPointData,
                                buttonData);
            }
 
 
            stylusPoints = new StylusPointCollection(stylusPointDescription, rawPointData, null, transform);
 
            // if we read too far into the stream (e.g. the packets were compressed)
            //      then move the stream pointer back to the end of the actual packet
            //      data before returning. This keeps the return value on the function
            //      (representing bytes read) honest and consistent with the stream
            //      position movement in this function.
            if (0 != locallyDecodedBytesRemaining)
            {
                inputStream.Seek(0 - (long)locallyDecodedBytesRemaining, SeekOrigin.Current);
            }
 
            return totalBytesInStrokeBlockOfIsfStream - locallyDecodedBytesRemaining;
        }
 
        
        /// <summary>
        /// Fill packet buffer with button data
        /// </summary>
        /// <param name="pointCount">how many points in the stroke</param>
        /// <param name="buttonCount">how many buttons</param>
        /// <param name="buttonIndex">the first index of the buttons in packets</param>
        /// <param name="packets">packet data</param>
        /// <param name="buttonData">button data to fill</param>
        private static void FillButtonData(
            int pointCount, 
            int buttonCount, 
            int buttonIndex, 
            int[] packets, 
            byte[] buttonData)
        {
            int intsPerPoint = buttonIndex + 1;
            int iPacket;
            int iDim = buttonIndex; //count of value properties
 
            // Get button data
            if (null != buttonData)
            {
                // Count of bytes per button
                int nBtnDim = (buttonCount + 7) / Native.BitsPerByte;
 
                // Count of complete integers
                int pointCountOfFullIntegers = (int)(nBtnDim / Native.SizeOfInt);
 
                // Remaining number of bytes in button data
                int pointCountOfPartialBytes = (int)(nBtnDim % Native.SizeOfInt);
 
                // Index into button byte array
                int iBtnByte = 0;
 
                // Copy the complete integers
                for (int iInt = 0; iInt < pointCountOfFullIntegers; ++iInt, ++iDim)
                {
                    // Index into button byte array
                    iBtnByte = (int)(iInt * Native.SizeOfInt);
                    iPacket = iDim;
 
                    // Get the button integers from each packet
                    for (int i = 0; i < pointCount; ++i, iPacket += intsPerPoint, iBtnByte += nBtnDim)
                    {
                        packets[iPacket] = ((int)((uint)((buttonData[iBtnByte] << (Native.BitsPerByte * 3)) | (buttonData[iBtnByte + 1] << (Native.BitsPerByte * 2)) | (buttonData[iBtnByte + 2] << Native.BitsPerByte) | (buttonData[iBtnByte + 3]))));
                    }
                }
 
                // If remaining number of bytes per button is non-negative,
                // construct button data from those bytes
                if (0 < pointCountOfPartialBytes)
                {
                    // Index into button byte array
                    iBtnByte = (int)(pointCountOfFullIntegers * Native.SizeOfInt);
                    iPacket = iDim;
 
                    // Constructs ints from partial button bytes
                    for (int i = 0; i < pointCount; ++i, iPacket += intsPerPoint, iBtnByte += nBtnDim)
                    {
                        // First byte will always be there
                        uint btn = buttonData[iBtnByte];
 
                        // For the remaining bytes, shift left 8 bits
                        for (int iPart = 1; iPart < pointCountOfPartialBytes; ++iPart)
                        {
                            btn = (btn << Native.BitsPerByte) | (buttonData[iBtnByte + iPart]);
                        }
 
                        // Assign the reconstructed button data to packet data
                        packets[iPacket] = (int)btn;
                    }
                }
            }
        }
 
 
 
        #endregion
 
        #endregion // Decoding
 
        #region Encoding
 
        #region Public Methods
#if OLD_ISF
        /// <summary>
        /// Returns an array of bytes of the saved stroke
        /// </summary>
        /// <param name="stroke">Stroke to save</param>
        /// <param name="stream">null to calculate only the size</param>
        /// <param name="compressor"></param>
        /// <param name="compressionAlgorithm"></param>
        /// <param name="guidList"></param>
        /// <param name="strokeLookupEntry"></param>
#else
        /// <summary>
        /// Returns an array of bytes of the saved stroke
        /// </summary>
        /// <param name="stroke">Stroke to save</param>
        /// <param name="stream">null to calculate only the size</param>
        /// <param name="compressionAlgorithm"></param>
        /// <param name="guidList"></param>
        /// <param name="strokeLookupEntry"></param>
#endif
        internal static uint EncodeStroke(
            Stroke stroke, 
            Stream stream, 
#if OLD_ISF
            Compressor compressor, 
#endif
            byte compressionAlgorithm, 
            GuidList guidList,
            StrokeCollectionSerializer.StrokeLookupEntry strokeLookupEntry)
        {
            uint cbWrite = SavePackets( stroke, 
                                        stream, 
#if OLD_ISF
                                        compressor, 
#endif
                                        strokeLookupEntry);
 
            if (stroke.ExtendedProperties.Count > 0)
                cbWrite += ExtendedPropertySerializer.EncodeAsISF(stroke.ExtendedProperties, stream, guidList, compressionAlgorithm, false);
 
            return cbWrite;
        }
 
        /// <summary>
        /// Builds the Stroke Descriptor for this stroke based on Packet Layout and Extended Properties
        /// For details on how this is strored please refer to the spec.
        /// </summary>
        internal static void BuildStrokeDescriptor(
            Stroke stroke, 
            GuidList guidList, 
            StrokeCollectionSerializer.StrokeLookupEntry strokeLookupEntry,
            out StrokeDescriptor strokeDescriptor, 
            out MetricBlock metricBlock)
        {
            // Initialize the metric block for this stroke
            metricBlock = new MetricBlock();
 
            // Clear any existing template
            strokeDescriptor = new StrokeDescriptor();
 
            // Uninitialized variable passed in AddMetricEntry
            MetricEntryType metricEntryType;
            StylusPointDescription stylusPointDescription = stroke.StylusPoints.Description;
 
            KnownTagCache.KnownTagIndex tag = guidList.FindTag(KnownIds.X, true);
            metricEntryType = metricBlock.AddMetricEntry(stylusPointDescription.GetPropertyInfo(StylusPointProperties.X), tag);
 
            tag = guidList.FindTag(KnownIds.Y, true);
            metricEntryType = metricBlock.AddMetricEntry(stylusPointDescription.GetPropertyInfo(StylusPointProperties.Y), tag);
 
            ReadOnlyCollection<StylusPointPropertyInfo> propertyInfos
                = stylusPointDescription.GetStylusPointProperties();
 
            int i = 0; //i is defined out of the for loop so we can use it later for buttons
            for (i = 2/*past x,y*/; i < propertyInfos.Count; i++)
            {
                if (i == StylusPointDescription.RequiredPressureIndex/*2*/ && 
                    !strokeLookupEntry.StorePressure)
                {
                    //
                    // don't store pressure information
                    //
                    continue;
                }
                StylusPointPropertyInfo propertyInfo = propertyInfos[i];
                if (propertyInfo.IsButton)
                {
                    //we don't serialize buttons
                    break;
                }
 
                tag = guidList.FindTag(propertyInfo.Id, true);
 
                strokeDescriptor.Template.Add(tag);
                strokeDescriptor.Size += SerializationHelper.VarSize((uint)tag);
 
                // Create the MetricEntry for this property if necessary
                metricEntryType = metricBlock.AddMetricEntry(propertyInfo, tag);
            }
 
            /*
            we drop button data on the floor. 
            int buttonCount = stylusPointDescription.ButtonCount;
            // Now write the button tags in the Template
            if (buttonCount > 0)
            {
                // First write the TAG_BUTTONS
                strokeDescriptor.Template.Add(KnownTagCache.KnownTagIndex.Buttons);
                strokeDescriptor.Size += SerializationHelper.VarSize((uint)KnownTagCache.KnownTagIndex.Buttons);
 
                // Next write the i of buttons
                strokeDescriptor.Template.Add((KnownTagCache.KnownTagIndex)buttonCount);
                strokeDescriptor.Size += SerializationHelper.VarSize((uint)buttonCount);
 
                //we broke above on i when it was a button, it still
                //points to the first button
                for (; i < propertyInfos.Count; i++)
                {
                    StylusPointPropertyInfo propertyInfo = propertyInfos[i];
                    tag = guidList.FindTag(propertyInfo.Id, false);
 
                    strokeDescriptor.Template.Add(tag);
                    strokeDescriptor.Size += SerializationHelper.VarSize((uint)tag);
                }
            }
            */
 
            // Now write the extended properties in the template
            if (stroke.ExtendedProperties.Count > 0)
            {
                strokeDescriptor.Template.Add(KnownTagCache.KnownTagIndex.StrokePropertyList);
                strokeDescriptor.Size += SerializationHelper.VarSize((uint)KnownTagCache.KnownTagIndex.StrokePropertyList);
 
                // Now write the tags corresponding to each extended properties of the stroke
                for (int x = 0; x < stroke.ExtendedProperties.Count; x++)
                {
                    tag = guidList.FindTag(stroke.ExtendedProperties[(int)x].Id, false);
 
                    strokeDescriptor.Template.Add(tag);
                    strokeDescriptor.Size += SerializationHelper.VarSize((uint)tag);
                }
            }
        }
 
        #endregion // Private Methods
 
        #region Private methods
#if OLD_ISF
        /// <summary>
        /// Saves the packets into a stream of bytes
        /// </summary>
        /// <param name="stroke">Stroke to save</param>
        /// <param name="stream">null to calculate size only</param>
        /// <param name="compressor"></param>
        /// <param name="strokeLookupEntry"></param>
        /// <returns></returns>
#else
        /// <summary>
        /// Saves the packets into a stream of bytes
        /// </summary>
        /// <param name="stroke">Stroke to save</param>
        /// <param name="stream">null to calculate size only</param>
        /// <param name="strokeLookupEntry"></param>
#endif
        static uint SavePackets(
            Stroke stroke,
            Stream stream, 
#if OLD_ISF
            Compressor compressor,
#endif
            StrokeCollectionSerializer.StrokeLookupEntry strokeLookupEntry)
        {
            // First write or calculate how many points are there
            uint pointCount = (uint)stroke.StylusPoints.Count;
            uint localBytesWritten = (stream != null) ? SerializationHelper.Encode(stream, pointCount) : SerializationHelper.VarSize(pointCount);
            byte compressionAlgorithm;
 
            int[][] outputArrays = strokeLookupEntry.ISFReadyStrokeData;
            //We don't serialize button data
            //int valuesPerPoint = stroke.StylusPoints.Description.GetOutputArrayLengthPerPoint();
            //int buttonCount = stroke.StylusPoints.Description.ButtonCount;
            
            ReadOnlyCollection<StylusPointPropertyInfo> propertyInfos
                = stroke.StylusPoints.Description.GetStylusPointProperties();
 
            int i = 0;
            for (; i < propertyInfos.Count; i++)
            {
                StylusPointPropertyInfo propertyInfo = propertyInfos[i];
                if (i == 2 && !strokeLookupEntry.StorePressure)
                {
                    //
                    // only store pressure if we need to
                    //
                    continue;
                }
                if (propertyInfo.IsButton)
                {
                    //
                    // we're at the buttons, handle this below
                    //
                    break;
                }
                compressionAlgorithm = strokeLookupEntry.CompressionData;
                localBytesWritten += SavePacketPropertyData(outputArrays[i], 
                                                            stream,
#if OLD_ISF
                                                            compressor, 
#endif
                                                            propertyInfo.Id, 
                                                            ref compressionAlgorithm);
            }
 
            /*
            We don't serialize button data
            // Now write all button data. Button data is stored as if it is another packet property
            // with size (cbuttoncount + 7)/8 bytes and corresponding guids are stored in the packet
            // description. Button data is only stored if buttons are present in the description and there
            // are packets in the stroke
            if (buttonCount > 0 && pointCount > 0)
            {
                Debug.Assert(i == valuesPerPoint - 1);
                BitStreamWriter bitWriter = new BitStreamWriter();
                //
                // Get the array of button data (i is still pointing at it)
                //
                int[] buttonData = outputArrays[i];
 
                for (int x = 0; x < pointCount; x++)
                {
                    //
                    // each int in the button data array contains buttonCount number
                    // of bits that need to be written to the BitStreamWriter
                    // the BitStreamWriter takes bytes at a time.  We always write the most
                    // signifigant bits first
                    //
                    int uncompactedButtonDataForPoint = buttonData[x];
 
                    // calculate the number of full bytes used for buttons per packet
                    //   Example: 10 buttons would require 1 full byte
                    //            but 8 would require 
                    int fullBytesForButtonsPerPacket = buttonCount / Native.BitsPerByte;
 
                    // calculate the number of bits that spill beyond the full byte boundary
                    //   Example: 10 buttons would require 2 extra bits (8 fit in a full byte)
                    int bitsToWrite = buttonCount % Native.BitsPerByte;
 
                    for (; fullBytesForButtonsPerPacket >= 0; fullBytesForButtonsPerPacket--)
                    {
                        byte byteOfButtonData = 
                            Convert.ToByte(uncompactedButtonDataForPoint >> (fullBytesForButtonsPerPacket * Native.BitsPerByte));
                        //
                        // write 8 or less bytes to the bitwriter
                        // checking for 0 handles the case where we're writing 8, 16 or 24 bytes
                        // and bitsToWrite is initialize to zero
                        //
                        if (bitsToWrite > 0)
                        {
                            bitWriter.Write(byteOfButtonData, bitsToWrite);
                        }
                        if (fullBytesForButtonsPerPacket > 0)
                        {
                            bitsToWrite = Native.BitsPerByte;
                        }
                    }
                }
 
                // retrieve the button bytes
                byte[] packedButtonData = bitWriter.ToBytes();
 
                if (packedButtonData.Length !=
                    ((buttonCount * pointCount + 7) / Native.BitsPerByte))
                {
                    throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Packed button length not equal to expected length"));
                }
 
                // write out the packed button data to the output stream
                stream.Write(packedButtonData, 0, packedButtonData.Length);
                localBytesWritten += (uint)packedButtonData.Length;
            }
            */
 
            return localBytesWritten;
        }
#if OLD_ISF
        /// <summary>
        /// Saves the packets data corresponding to a packet property (identified by the guid) into the stream
        /// based on the Compression algorithm and compress header
        /// </summary>
        /// <param name="packetdata">packet data to save</param>
        /// <param name="stream">null to calculate only the size</param>
        /// <param name="compressor"></param>
        /// <param name="guid"></param>
        /// <param name="algo"></param>
        /// <returns></returns>
#else
        /// <summary>
        /// Saves the packets data corresponding to a packet property (identified by the guid) into the stream
        /// based on the Compression algorithm and compress header
        /// </summary>
        /// <param name="packetdata">packet data to save</param>
        /// <param name="stream">null to calculate only the size</param>
        /// <param name="guid"></param>
        /// <param name="algo"></param>
#endif
        static uint SavePacketPropertyData(
            int[] packetdata, 
            Stream stream, 
#if OLD_ISF
            Compressor compressor, 
#endif
            Guid guid, 
            ref byte algo)
        {
            if (packetdata.Length == 0)
            {
                return 0;
            }
 
            byte[] data = 
                Compressor.CompressPacketData(
#if OLD_ISF
                        compressor, 
#endif
                        packetdata, 
                        ref algo);
 
            Debug.Assert(stream != null);
            // Now write the data in the stream
            stream.Write(data, 0, (int)data.Length);
 
            return (uint)data.Length;
        }
 
        #endregion
 
        #endregion // Encoding
    }
}