File: MS\Internal\Media\ParserStreamGeometryContext.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.
 
//
// This class is used to compress a Path to BAML.
//
//  At compile-time this api is called into to "flatten" graphics calls to a BinaryWriter
//  At run-time this api is called into to rehydrate the flattened graphics calls
//        via invoking methods on a supplied StreamGeometryContext.
//
//  Via this compression - we reduce the time spent parsing at startup, we create smaller baml,
//  and we reduce creation of temporary strings.
//
 
using MS.Internal;
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security;
using System.IO;
using MS.Utility;
 
#if PBTCOMPILER

using MS.Internal.Markup;
 
namespace MS.Internal.Markup
#else
 
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using MS.Internal.PresentationCore;
 
namespace MS.Internal.Media
#endif
{
     /// <summary>
     ///     ParserStreamGeometryContext
     /// </summary>
     internal class ParserStreamGeometryContext : StreamGeometryContext
     {
        enum ParserGeometryContextOpCodes : byte
        {
            BeginFigure = 0,
            LineTo = 1,
            QuadraticBezierTo = 2,
            BezierTo = 3,
            PolyLineTo = 4,
            PolyQuadraticBezierTo = 5,
            PolyBezierTo = 6,
            ArcTo = 7,
            Closed = 8,
            FillRule = 9,
        }
 
        private const byte HighNibble = 0xF0;
        private const byte LowNibble = 0x0F;
 
        private const byte SetBool1 = 0x10; // 00010000
        private const byte SetBool2 = 0x20; // 00100000
        private const byte SetBool3 = 0x40; // 01000000
        private const byte SetBool4 = 0x80; // 10000000
 
        #region Constructors
 
        /// <summary>
        /// This constructor exists to prevent external derivation
        /// </summary>
        internal ParserStreamGeometryContext(BinaryWriter bw)
        {
            _bw = bw;
        }
 
        #endregion Constructors
 
 
        #region internal Methods
 
#if PRESENTATION_CORE
        internal void SetFillRule(FillRule fillRule)
#else
        internal void SetFillRule(bool boolFillRule)
#endif
        {
#if PRESENTATION_CORE
            bool boolFillRule = FillRuleToBool(fillRule);
#endif
 
            byte packedByte = PackByte(ParserGeometryContextOpCodes.FillRule, boolFillRule, false);
 
            _bw.Write(packedByte);
        }
 
        /// <summary>
        /// BeginFigure - Start a new figure.
        /// </summary>
        /// <remarks>
        /// Stored as [PointAndTwoBools] (see SerializepointAndTwoBools method).
        /// </remarks>
 
        public override void BeginFigure(Point startPoint, bool isFilled, bool isClosed)
        {
            //
            // We need to update the BeginFigure block of the last figure (if
            // there was one).
            //
            FinishFigure();
 
            _startPoint = startPoint;
            _isFilled = isFilled;
            _isClosed = isClosed;
 
            _figureStreamPosition = CurrentStreamPosition;
 
            //
            // This will be overwritten later when we start the next figure (i.e. when we're sure isClosed isn't
            // going to change). We write it out now to ensure that we reserve exactly the right amount of space.
            // Note that the number of bytes written is dependant on the particular value of startPoint, since
            // we can compress doubles when they are in fact integral.
            //
            SerializePointAndTwoBools(ParserGeometryContextOpCodes.BeginFigure, startPoint, isFilled, isClosed);
        }
 
        /// <summary>
        /// LineTo - append a LineTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [PointAndTwoBools] (see SerializepointAndTwoBools method).
        /// </remarks>
        public override void LineTo(Point point, bool isStroked, bool isSmoothJoin)
        {
            SerializePointAndTwoBools(ParserGeometryContextOpCodes.LineTo, point, isStroked, isSmoothJoin);
        }
 
        /// <summary>
        /// QuadraticBezierTo - append a QuadraticBezierTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [PointAndTwoBools] [Number] [Number]
        /// </remarks>
        public override void QuadraticBezierTo(Point point1, Point point2, bool isStroked, bool isSmoothJoin)
        {
            SerializePointAndTwoBools(ParserGeometryContextOpCodes.QuadraticBezierTo, point1, isStroked, isSmoothJoin);
 
            XamlSerializationHelper.WriteDouble(_bw, point2.X);
            XamlSerializationHelper.WriteDouble(_bw, point2.Y);
        }
 
        /// <summary>
        /// BezierTo - apply a BezierTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [PointAndTwoBools] [Number] [Number] [Number] [Number]
        /// </remarks>
        public override void BezierTo(Point point1, Point point2, Point point3, bool isStroked, bool isSmoothJoin)
        {
            SerializePointAndTwoBools(ParserGeometryContextOpCodes.BezierTo, point1, isStroked, isSmoothJoin);
 
            XamlSerializationHelper.WriteDouble(_bw, point2.X);
            XamlSerializationHelper.WriteDouble(_bw, point2.Y);
 
            XamlSerializationHelper.WriteDouble(_bw, point3.X);
            XamlSerializationHelper.WriteDouble(_bw, point3.Y);
        }
 
        /// <summary>
        /// PolyLineTo - append a PolyLineTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [ListOfPointAndTwoBools] (see SerializeListOfPointsAndTwoBools method).
        /// </remarks>
        public override void PolyLineTo(IList<Point> points, bool isStroked, bool isSmoothJoin)
        {
            SerializeListOfPointsAndTwoBools(ParserGeometryContextOpCodes.PolyLineTo, points, isStroked, isSmoothJoin);
        }
 
        /// <summary>
        /// PolyQuadraticBezierTo - append a PolyQuadraticBezierTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [ListOfPointAndTwoBools] (see SerializeListOfPointsAndTwoBools method).
        /// </remarks>
        public override void PolyQuadraticBezierTo(IList<Point> points, bool isStroked, bool isSmoothJoin)
        {
            SerializeListOfPointsAndTwoBools(ParserGeometryContextOpCodes.PolyQuadraticBezierTo, points, isStroked, isSmoothJoin);
        }
 
        /// <summary>
        /// PolyBezierTo - append a PolyBezierTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [ListOfPointAndTwoBools] (see SerializeListOfPointsAndTwoBools method).
        /// </remarks>
        public override void PolyBezierTo(IList<Point> points, bool isStroked, bool isSmoothJoin)
        {
            SerializeListOfPointsAndTwoBools(ParserGeometryContextOpCodes.PolyBezierTo, points, isStroked, isSmoothJoin);
        }
 
        /// <summary>
        /// ArcTo - append an ArcTo to the current figure.
        /// </summary>
        /// <remarks>
        /// Stored as [PointAndTwoBools] [Packed byte for isLargeArc and sweepDirection] [Pair of Numbers for Size] [Pair of Numbers for rotation Angle]
        ///
        ///     Also note that we've special cased this method signature to avoid moving the enum for SweepDirection into PBT (will require codegen changes).
        /// </remarks>
#if PBTCOMPILER
        public override void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, bool sweepDirection, bool isStroked, bool isSmoothJoin)
#else
        public override void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked, bool isSmoothJoin)
#endif
        {
            SerializePointAndTwoBools(ParserGeometryContextOpCodes.ArcTo, point, isStroked, isSmoothJoin);
 
            //
            // Pack isLargeArc & sweepDirection into a single byte.
            //
            byte packMe = 0;
            if (isLargeArc)
            {
                packMe = LowNibble;
            }
 
#if PBTCOMPILER
            if (sweepDirection)
#else
            if (SweepToBool(sweepDirection))
#endif
            {
                packMe |= HighNibble;
            }
 
            _bw.Write(packMe);
 
            //
            // Write out Size & Rotation Angle.
            //
            XamlSerializationHelper.WriteDouble(_bw, size.Width);
            XamlSerializationHelper.WriteDouble(_bw, size.Height);
            XamlSerializationHelper.WriteDouble(_bw, rotationAngle);
        }
 
        internal bool FigurePending
        {
            get
            {
                return (_figureStreamPosition > -1);
            }
        }
 
        internal int CurrentStreamPosition
        {
            get
            {
                return checked((int)_bw.Seek(0, SeekOrigin.Current));
            }
        }
 
        internal void FinishFigure()
        {
            if (FigurePending)
            {
                int currentOffset = CurrentStreamPosition;
 
                //
                // Go back and overwrite our existing begin figure block. See comment in BeginFigure.
                //
                _bw.Seek(_figureStreamPosition, SeekOrigin.Begin);
                SerializePointAndTwoBools(ParserGeometryContextOpCodes.BeginFigure, _startPoint, _isFilled, _isClosed);
 
                _bw.Seek(currentOffset, SeekOrigin.Begin);
            }
        }
 
        /// <summary>
        /// This is the same as the Close call:
        /// Closes the Context and flushes the content.
        /// Afterwards the Context can not be used anymore.
        /// This call does not require all Push calls to have been Popped.
        /// </summary>
        internal override void DisposeCore()
        {
        }
 
        /// <summary>
        /// SetClosedState - Sets the current closed state of the figure.
        /// </summary>
        internal override void SetClosedState(bool closed)
        {
            _isClosed = closed;
        }
 
        /// <summary>
        ///     Mark that the stream is Done.
        /// </summary>
        internal void MarkEOF()
        {
            //
            // We need to update the BeginFigure block of the last figure (if
            // there was one).
            //
            FinishFigure();
            _bw.Write((byte) ParserGeometryContextOpCodes.Closed);
        }
 
#if PRESENTATION_CORE
        internal static void Deserialize(BinaryReader br, StreamGeometryContext sc, StreamGeometry geometry)
        {
            bool closed = false;
            Byte currentByte;
 
            while (!closed)
            {
                currentByte = br.ReadByte();
 
                ParserGeometryContextOpCodes opCode = UnPackOpCode(currentByte);
 
                switch(opCode)
                {
                    case ParserGeometryContextOpCodes.FillRule :
                        DeserializeFillRule(br, currentByte, geometry);
                        break;
 
                    case ParserGeometryContextOpCodes.BeginFigure :
                        DeserializeBeginFigure(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.LineTo :
                        DeserializeLineTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.QuadraticBezierTo :
                        DeserializeQuadraticBezierTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.BezierTo :
                        DeserializeBezierTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.PolyLineTo :
                        DeserializePolyLineTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.PolyQuadraticBezierTo :
                        DeserializePolyQuadraticBezierTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.PolyBezierTo :
                        DeserializePolyBezierTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.ArcTo :
                        DeserializeArcTo(br, currentByte, sc);
                        break;
 
                    case ParserGeometryContextOpCodes.Closed :
                        closed = true;
                        break;
                }
            }
        }
#endif
        #endregion internal Methods
 
        #region private Methods
 
        //
        // Deserialization Methods.
        //
        // These are only required at "runtime" - therefore only in PRESENTATION_CORE
        //
 
#if PRESENTATION_CORE
 
        private static void DeserializeFillRule(BinaryReader br, Byte firstByte, StreamGeometry geometry)
        {
            bool boolFillRule;
            bool unused;
            FillRule fillRule;
 
            UnPackBools(firstByte, out boolFillRule, out unused);
 
            fillRule = BoolToFillRule(boolFillRule);
 
            geometry.FillRule = fillRule;
}
 
        private static void DeserializeBeginFigure(BinaryReader br, Byte firstByte, StreamGeometryContext sc)
        {
            Point point;
            bool isFilled;
            bool isClosed;
 
            DeserializePointAndTwoBools(br, firstByte, out point, out isFilled, out isClosed);
 
            sc.BeginFigure(point, isFilled, isClosed);
        }
 
        private static void DeserializeLineTo(BinaryReader br, Byte firstByte, StreamGeometryContext sc)
        {
            Point point;
            bool isStroked;
            bool isSmoothJoin;
 
            DeserializePointAndTwoBools(br, firstByte, out point, out isStroked, out isSmoothJoin);
 
            sc.LineTo(point, isStroked, isSmoothJoin);
        }
 
        private static void DeserializeQuadraticBezierTo(BinaryReader br, byte firstByte, StreamGeometryContext sc)
        {
            Point point1;
            Point point2 = new Point();
            bool isStroked;
            bool isSmoothJoin;
 
            DeserializePointAndTwoBools(br, firstByte, out point1, out isStroked, out isSmoothJoin);
 
            point2.X = XamlSerializationHelper.ReadDouble(br);
            point2.Y = XamlSerializationHelper.ReadDouble(br);
 
            sc.QuadraticBezierTo(point1, point2, isStroked, isSmoothJoin);
        }
 
        private static void DeserializeBezierTo(BinaryReader br, byte firstByte, StreamGeometryContext sc)
        {
            Point point1;
            Point point2 = new Point();
            Point point3 = new Point();
 
            bool isStroked;
            bool isSmoothJoin;
 
            DeserializePointAndTwoBools(br, firstByte, out point1, out isStroked, out isSmoothJoin);
 
            point2.X = XamlSerializationHelper.ReadDouble(br);
            point2.Y = XamlSerializationHelper.ReadDouble(br);
 
            point3.X = XamlSerializationHelper.ReadDouble(br);
            point3.Y = XamlSerializationHelper.ReadDouble(br);
 
            sc.BezierTo(point1, point2, point3, isStroked, isSmoothJoin);
        }
 
        private static void DeserializePolyLineTo(BinaryReader br, Byte firstByte, StreamGeometryContext sc)
        {
            bool isStroked;
            bool isSmoothJoin;
            IList<Point> points;
 
            points = DeserializeListOfPointsAndTwoBools(br, firstByte, out isStroked, out isSmoothJoin);
 
            sc.PolyLineTo(points, isStroked, isSmoothJoin);
        }
 
        private static void DeserializePolyQuadraticBezierTo(BinaryReader br, Byte firstByte, StreamGeometryContext sc)
        {
            bool isStroked;
            bool isSmoothJoin;
            IList<Point> points;
 
            points = DeserializeListOfPointsAndTwoBools(br, firstByte, out isStroked, out isSmoothJoin);
 
            sc.PolyQuadraticBezierTo(points, isStroked, isSmoothJoin);
        }
 
        private static void DeserializePolyBezierTo(BinaryReader br, Byte firstByte, StreamGeometryContext sc)
        {
            bool isStroked;
            bool isSmoothJoin;
            IList<Point> points;
 
            points = DeserializeListOfPointsAndTwoBools(br, firstByte, out isStroked, out isSmoothJoin);
 
            sc.PolyBezierTo(points, isStroked, isSmoothJoin);
        }
 
 
        private static void DeserializeArcTo(BinaryReader br, byte firstByte, StreamGeometryContext sc)
        {
            Point point;
            Size size = new Size();
            double rotationAngle;
            bool isStroked;
            bool isSmoothJoin;
            bool isLargeArc;
            SweepDirection sweepDirection;
 
            DeserializePointAndTwoBools(br, firstByte, out point, out isStroked, out isSmoothJoin);
 
            // Read the packed byte for isLargeArd & sweepDirection.
 
            //
            // Pack isLargeArc & sweepDirection into a signle byte.
            //
            byte packedByte = br.ReadByte();
 
            isLargeArc = ((packedByte & LowNibble) != 0);
 
            sweepDirection = BoolToSweep(((packedByte & HighNibble) != 0));
 
 
            size.Width = XamlSerializationHelper.ReadDouble(br);
            size.Height = XamlSerializationHelper.ReadDouble(br);
            rotationAngle = XamlSerializationHelper.ReadDouble(br);
 
            sc.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection, isStroked, isSmoothJoin);
        }
 
        //
        //  Private Deserialization helpers.
        //
 
        private static void UnPackBools(byte packedByte, out bool bool1, out bool bool2)
        {
            bool1 = (packedByte & SetBool1) != 0;
            bool2 = (packedByte & SetBool2) != 0;
        }
 
        private static void UnPackBools(byte packedByte, out bool bool1, out bool bool2, out bool bool3, out bool bool4)
        {
            bool1 = (packedByte & SetBool1) != 0;
            bool2 = (packedByte & SetBool2) != 0;
            bool3 = (packedByte & SetBool3) != 0;
            bool4 = (packedByte & SetBool4) != 0;
        }
 
        private static ParserGeometryContextOpCodes UnPackOpCode(byte packedByte)
        {
            return ((ParserGeometryContextOpCodes) (packedByte & 0x0F));
        }
 
        private static IList<Point> DeserializeListOfPointsAndTwoBools(BinaryReader br, Byte firstByte, out bool bool1, out bool bool2)
        {
            int count;
            IList<Point> points;
            Point point;
 
            // Pack the two bools into one byte
            UnPackBools(firstByte, out bool1, out bool2);
 
            count = br.ReadInt32();
 
            points = new List<Point>(count);
 
            for(int i = 0; i < count; i++)
            {
                point = new Point(XamlSerializationHelper.ReadDouble(br),
                                  XamlSerializationHelper.ReadDouble(br));
 
                points.Add(point);
            }
 
            return points;
        }
 
 
        private static void DeserializePointAndTwoBools(BinaryReader br, Byte firstByte, out Point point, out bool bool1, out bool bool2)
        {
            bool isScaledIntegerX = false;
            bool isScaledIntegerY = false;
 
            UnPackBools(firstByte, out bool1, out bool2, out isScaledIntegerX, out isScaledIntegerY);
 
            point = new Point(DeserializeDouble(br, isScaledIntegerX),
                              DeserializeDouble(br, isScaledIntegerY));
        }
 
        private static Double DeserializeDouble(BinaryReader br, bool isScaledInt)
        {
            if (isScaledInt)
            {
                return XamlSerializationHelper.ReadScaledInteger(br);
            }
            else
            {
                return XamlSerializationHelper.ReadDouble(br);
            }
        }
 
        //
        // Private serialization helpers
        //
 
        private static SweepDirection BoolToSweep(bool value)
        {
            if(!value)
                return SweepDirection.Counterclockwise;
            else
                return SweepDirection.Clockwise;
        }
 
        private static bool SweepToBool(SweepDirection sweep)
        {
            if (sweep == SweepDirection.Counterclockwise)
                return false;
            else
                return true;
        }
 
        private static FillRule BoolToFillRule(bool value)
        {
            if(!value)
                return FillRule.EvenOdd;
            else
                return FillRule.Nonzero;
        }
 
        private static bool FillRuleToBool(FillRule fill)
        {
            if (fill == FillRule.EvenOdd)
                return false;
            else
                return true;
        }
 
#endif
 
        //
        // SerializePointAndTwoBools
        //
        // Binary format is :
        //
        //  <Byte+OpCode> <Number1> <Number2>
        //
        //      Where :
        //          <Byte+OpCode> := OpCode + bool1 + bool2 + isScaledIntegerX + isScaledIntegerY
        //          <NumberN> := <ScaledInteger> | <SerializationFloatTypeForSpecialNumbers> | <SerializationFloatType.Double+Double>
        //          <SerializationFloatTypeForSpecialNumbers> := <SerializationFloatType.Zero> | <SerializationFloatType.One> | <SerializationFloatType.MinusOne>
        //          <SerializationFloatType.Double+Double> := <SerializationFloatType.Double> <Double>
        //
        // By packing the flags for isScaledInteger into the first byte - we save 2 extra bytes per number for the common case.
        //
        // As a result - most LineTo's (and other operations) will be stored in 9 bytes.
        //               Some LineTo's will be 6 (or even sometimes 3)
        //               Max LineTo will be 19 (two doubles).
        private void SerializePointAndTwoBools(ParserGeometryContextOpCodes opCode,
                                                       Point point,
                                                       bool bool1,
                                                       bool bool2)
        {
            int intValueX = 0;
            int intValueY = 0;
            bool isScaledIntegerX, isScaledIntegerY;
 
            isScaledIntegerX = XamlSerializationHelper.CanConvertToInteger(point.X, ref intValueX);
            isScaledIntegerY = XamlSerializationHelper.CanConvertToInteger(point.Y, ref intValueY);
 
            _bw.Write(PackByte(opCode, bool1, bool2, isScaledIntegerX, isScaledIntegerY));
 
            SerializeDouble(point.X, isScaledIntegerX, intValueX);
            SerializeDouble(point.Y, isScaledIntegerY, intValueY);
        }
 
        // SerializeListOfPointsAndTwoBools
        //
        // Binary format is :
        //
        //  <Byte+OpCode> <Count> <Number1> ... <NumberN>
        //
        //      <Byte+OpCode> := OpCode + bool1 + bool2
        //      <Count> := int32
        //      <NumberN> := <SerializationFloatType.ScaledInteger+Integer> | <SerializationFloatTypeForSpecialNumbers> | <SerializationFloatType.Double+Double>
        private void SerializeListOfPointsAndTwoBools(ParserGeometryContextOpCodes opCode, IList<Point> points, bool bool1, bool bool2)
        {
            // Pack the two bools into one byte
            Byte packedByte = PackByte(opCode, bool1, bool2);
            _bw.Write(packedByte);
 
            // Write the count.
            _bw.Write(points.Count);
 
            // Write out all the Points
            for(int i = 0; i < points.Count; i++)
            {
                XamlSerializationHelper.WriteDouble(_bw, points[i].X);
                XamlSerializationHelper.WriteDouble(_bw, points[i].Y);
            }
        }
 
        private void SerializeDouble(double value, bool isScaledInt, int scaledIntValue)
        {
            if (isScaledInt)
            {
                _bw.Write(scaledIntValue);
            }
            else
            {
                XamlSerializationHelper.WriteDouble(_bw, value);
            }
        }
 
        private static byte PackByte(ParserGeometryContextOpCodes opCode, bool bool1, bool bool2)
        {
            return PackByte(opCode, bool1, bool2, false, false);
        }
 
 
        // PackByte
        //      Packs an op-code, and up to 4 booleans into a single byte.
        //
        // Binary format is :
        //      First 4 bits map directly to the op-code.
        //      Next 4 bits map to booleans 1 - 4.
        //
        //          Like this:
        //
        //              7| 6  | 5  | 4  | 3 | 2 | 1 | 0 |
        //           <B4>|<B3>|<B2>|<B1><-  Op Code    ->
        //
        private static byte PackByte(ParserGeometryContextOpCodes opCode, bool bool1, bool bool2, bool bool3, bool bool4)
        {
            byte packedByte = (byte) opCode;
 
            if (packedByte >= 16)
            {
                throw new ArgumentException(SR.UnknownPathOperationType);
            }
 
            if (bool1)
            {
                packedByte |= SetBool1;
            }
 
            if (bool2)
            {
                packedByte |= SetBool2;
            }
 
            if (bool3)
            {
                packedByte |= SetBool3;
            }
 
            if (bool4)
            {
                packedByte |= SetBool4;
            }
 
            return packedByte;
        }
 
        #endregion private Methods
 
        private BinaryWriter _bw;
 
        Point _startPoint;
        bool _isClosed;
        bool _isFilled;
 
        int _figureStreamPosition = -1;
    }
}