StrokeSerializer.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / MS / Internal / Ink / InkSerializedFormat / StrokeSerializer.cs / 1 / StrokeSerializer.cs

                            //#define OLD_ISF 
//------------------------------------------------------------------------
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
//  
//-----------------------------------------------------------------------
 
using System; 
using System.IO;
using System.Security; 
using System.Diagnostics;
using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Windows.Media;
using System.Windows.Input; 
using System.Windows.Ink; 
using MS.Internal.Ink;
using MS.Internal.IO.Packaging; 

namespace MS.Internal.Ink.InkSerializedFormat
{
    internal static class StrokeSerializer 
    {
        #region Decoding 
 
        #region Public Methods
#if OLD_ISF 
        /// 
        /// Loads a stroke from the stream based on Stroke Descriptor, StylusPointDescription, Drawing Attributes, Stroke IDs, transform and GuidList
        /// 
        ///  
        /// 
        ///  
        ///  
        /// 
        ///  
        /// 
        /// Compression module
        /// Newly decoded stroke
        ///  
        /// 
        ///     Critical - calls the StrokeSerializer.DecodeISFIntoStroke critical method 
        /// 
        ///     Called directly by  StrokeCollectionSerializer.DecodeRawISF
        /// 
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.DecodeRawISF
        /// 
        [SecurityCritical]
#else 
        /// 
        /// Loads a stroke from the stream based on Stroke Descriptor, StylusPointDescription, Drawing Attributes, Stroke IDs, transform and GuidList 
        ///  
        /// 
        ///  
        /// 
        /// 
        /// 
        ///  
        /// 
        /// Newly decoded stroke 
#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
        /// 
        /// 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.
        ///  
        /// 
        /// 
        /// 
        ///  
        /// 
        ///  
        ///  
        /// 
        ///  
        /// 
        /// 
        ///     Critical - calls the ExtendedPropertySerializer.DecodeAsISF
        ///                          StrokeSerializer.LoadPackets 
        ///                      and Compressor.DecompressPropertyData critical methods
        /// 
        ///     Called directly by  StrokeSerializer.DecodeStroke 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.DecodeRawISF 
        /// 
       [SecurityCritical]
#else
        ///  
        /// 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. 
        /// 
        /// 
        /// 
        ///  
        /// 
        ///  
        ///  
        /// 
        ///  
#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 
        /// 
        /// Loads packets from the input stream.  For example, packets are all of the x's in a stroke
        /// 
        ///  
        ///     Critical - calls the Compressor.DecompressPacketData critical method
        /// 
        ///     Called directly by  StrokeSerializer.DecodeISFIntoStroke 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.DecodeRawISF 
        /// 
        [SecurityCritical]
#else
        ///  
        /// Loads packets from the input stream.  For example, packets are all of the x's in a stroke
        ///  
#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;
        } 

 
        ///  
        /// Fill packet buffer with button data
        ///  
        /// how many points in the stroke
        /// how many buttons
        /// the first index of the buttons in packets
        /// packet data 
        /// button data to fill
        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
        /// 
        /// Returns an array of bytes of the saved stroke
        ///  
        /// Stroke to save
        /// null to calculate only the size 
        ///  
        /// 
        ///  
        /// 
        /// 
        ///     Critical - Calls the critical methods
        ///                     StrokeSerializer.SavePackets 
        ///                     ExtendedPropertySerializer.EncodeAsISF
        /// 
        ///     This directly called by StrokeCollectionSerializer.StoreStrokeData 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.EncodeISF 
        ///
        /// 
        [SecurityCritical]
#else 
        /// 
        /// Returns an array of bytes of the saved stroke 
        ///  
        /// Stroke to save
        /// null to calculate only the size 
        /// 
        /// 
        /// 
#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; 
        }
 
        /// 
        /// 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.
        ///  
        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 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.  See Windows OS Bugs 1413460 for details
            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 
        ///  
        /// Saves the packets into a stream of bytes
        ///  
        /// Stroke to save
        /// null to calculate size only
        /// 
        ///  
        /// 
        ///  
        ///     Critical - Calls the critical method StrokeSerializer.SavePacketPropertyData 
        ///
        ///     This directly called by StrokeSerializer.EncodeStroke 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.EncodeISF
        ///
        ///  
        [SecurityCritical]
#else 
        ///  
        /// Saves the packets into a stream of bytes
        ///  
        /// Stroke to save
        /// null to calculate size only
        /// 
#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, see Windows OS Bugs 1413460 for details
            //int valuesPerPoint = stroke.StylusPoints.Description.GetOutputArrayLengthPerPoint();
            //int buttonCount = stroke.StylusPoints.Description.ButtonCount;
 
            ReadOnlyCollection 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, see Windows OS Bugs 1413460 for details
            // 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
        ///  
        /// Saves the packets data corresponding to a packet property (identified by the guid) into the stream
        /// based on the Compression algorithm and compress header
        /// 
        /// packet data to save 
        /// null to calculate only the size
        ///  
        ///  
        /// 
        ///  
        /// 
        ///     Critical - Calls unmanaged code in Compressor.CompressPacketData to compress
        ///         an int[] representing packet data
        /// 
        ///     This directly called by StrokeSerializer.SavePackets
        /// 
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.EncodeISF 
        ///
        ///  
        [SecurityCritical]
#else
        /// 
        /// Saves the packets data corresponding to a packet property (identified by the guid) into the stream 
        /// based on the Compression algorithm and compress header
        ///  
        /// packet data to save 
        /// null to calculate only the size
        ///  
        /// 
#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 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//#define OLD_ISF 
//------------------------------------------------------------------------
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
//  
//-----------------------------------------------------------------------
 
using System; 
using System.IO;
using System.Security; 
using System.Diagnostics;
using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.Windows.Media;
using System.Windows.Input; 
using System.Windows.Ink; 
using MS.Internal.Ink;
using MS.Internal.IO.Packaging; 

namespace MS.Internal.Ink.InkSerializedFormat
{
    internal static class StrokeSerializer 
    {
        #region Decoding 
 
        #region Public Methods
#if OLD_ISF 
        /// 
        /// Loads a stroke from the stream based on Stroke Descriptor, StylusPointDescription, Drawing Attributes, Stroke IDs, transform and GuidList
        /// 
        ///  
        /// 
        ///  
        ///  
        /// 
        ///  
        /// 
        /// Compression module
        /// Newly decoded stroke
        ///  
        /// 
        ///     Critical - calls the StrokeSerializer.DecodeISFIntoStroke critical method 
        /// 
        ///     Called directly by  StrokeCollectionSerializer.DecodeRawISF
        /// 
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.DecodeRawISF
        /// 
        [SecurityCritical]
#else 
        /// 
        /// Loads a stroke from the stream based on Stroke Descriptor, StylusPointDescription, Drawing Attributes, Stroke IDs, transform and GuidList 
        ///  
        /// 
        ///  
        /// 
        /// 
        /// 
        ///  
        /// 
        /// Newly decoded stroke 
#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
        /// 
        /// 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.
        ///  
        /// 
        /// 
        /// 
        ///  
        /// 
        ///  
        ///  
        /// 
        ///  
        /// 
        /// 
        ///     Critical - calls the ExtendedPropertySerializer.DecodeAsISF
        ///                          StrokeSerializer.LoadPackets 
        ///                      and Compressor.DecompressPropertyData critical methods
        /// 
        ///     Called directly by  StrokeSerializer.DecodeStroke 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.DecodeRawISF 
        /// 
       [SecurityCritical]
#else
        ///  
        /// 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. 
        /// 
        /// 
        /// 
        ///  
        /// 
        ///  
        ///  
        /// 
        ///  
#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 
        /// 
        /// Loads packets from the input stream.  For example, packets are all of the x's in a stroke
        /// 
        ///  
        ///     Critical - calls the Compressor.DecompressPacketData critical method
        /// 
        ///     Called directly by  StrokeSerializer.DecodeISFIntoStroke 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.DecodeRawISF 
        /// 
        [SecurityCritical]
#else
        ///  
        /// Loads packets from the input stream.  For example, packets are all of the x's in a stroke
        ///  
#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;
        } 

 
        ///  
        /// Fill packet buffer with button data
        ///  
        /// how many points in the stroke
        /// how many buttons
        /// the first index of the buttons in packets
        /// packet data 
        /// button data to fill
        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
        /// 
        /// Returns an array of bytes of the saved stroke
        ///  
        /// Stroke to save
        /// null to calculate only the size 
        ///  
        /// 
        ///  
        /// 
        /// 
        ///     Critical - Calls the critical methods
        ///                     StrokeSerializer.SavePackets 
        ///                     ExtendedPropertySerializer.EncodeAsISF
        /// 
        ///     This directly called by StrokeCollectionSerializer.StoreStrokeData 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.EncodeISF 
        ///
        /// 
        [SecurityCritical]
#else 
        /// 
        /// Returns an array of bytes of the saved stroke 
        ///  
        /// Stroke to save
        /// null to calculate only the size 
        /// 
        /// 
        /// 
#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; 
        }
 
        /// 
        /// 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.
        ///  
        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 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.  See Windows OS Bugs 1413460 for details
            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 
        ///  
        /// Saves the packets into a stream of bytes
        ///  
        /// Stroke to save
        /// null to calculate size only
        /// 
        ///  
        /// 
        ///  
        ///     Critical - Calls the critical method StrokeSerializer.SavePacketPropertyData 
        ///
        ///     This directly called by StrokeSerializer.EncodeStroke 
        ///
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.EncodeISF
        ///
        ///  
        [SecurityCritical]
#else 
        ///  
        /// Saves the packets into a stream of bytes
        ///  
        /// Stroke to save
        /// null to calculate size only
        /// 
#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, see Windows OS Bugs 1413460 for details
            //int valuesPerPoint = stroke.StylusPoints.Description.GetOutputArrayLengthPerPoint();
            //int buttonCount = stroke.StylusPoints.Description.ButtonCount;
 
            ReadOnlyCollection 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, see Windows OS Bugs 1413460 for details
            // 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
        ///  
        /// Saves the packets data corresponding to a packet property (identified by the guid) into the stream
        /// based on the Compression algorithm and compress header
        /// 
        /// packet data to save 
        /// null to calculate only the size
        ///  
        ///  
        /// 
        ///  
        /// 
        ///     Critical - Calls unmanaged code in Compressor.CompressPacketData to compress
        ///         an int[] representing packet data
        /// 
        ///     This directly called by StrokeSerializer.SavePackets
        /// 
        ///     TreatAsSafe boundary is StrokeCollectionSerializer.EncodeISF 
        ///
        ///  
        [SecurityCritical]
#else
        /// 
        /// Saves the packets data corresponding to a packet property (identified by the guid) into the stream 
        /// based on the Compression algorithm and compress header
        ///  
        /// packet data to save 
        /// null to calculate only the size
        ///  
        /// 
#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 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK