private void ResolveKeyTimes()
        {
            Debug.Assert(!_areKeyTimesValid, "KeyFrameCharAnimaton.ResolveKeyTimes() shouldn't be called if the key times are already valid.");

            int keyFrameCount = 0;

            if (_keyFrames != null)
            {
                keyFrameCount = _keyFrames.Count;
            }

            if (keyFrameCount == 0)
            {
                _sortedResolvedKeyFrames = null;
                _areKeyTimesValid = true;
                return;
            }

            _sortedResolvedKeyFrames = new ResolvedKeyFrameEntry[keyFrameCount];

            int index = 0;

            // Initialize the _originalKeyFrameIndex.
            for ( ; index < keyFrameCount; index++)
            {
                _sortedResolvedKeyFrames[index]._originalKeyFrameIndex = index;
            }

            // calculationDuration represents the time span we will use to resolve
            // percent key times. This is defined as the value in the following
            // precedence order:
            //   1. The animation's duration, but only if it is a time span, not auto or forever.
            //   2. The largest time span specified key time of all the key frames.
            //   3. 1 second, to match the From/To/By animations.

            TimeSpan calculationDuration = TimeSpan.Zero;

            Duration duration = Duration;

            if (duration.HasTimeSpan)
            {
                calculationDuration = duration.TimeSpan;
            }
            else
            {
                calculationDuration = LargestTimeSpanKeyTime;
            }

            int maxKeyFrameIndex = keyFrameCount - 1;
            ArrayList unspecifiedBlocks = new ArrayList();
            bool hasPacedKeyTimes = false;

            //
            // Pass 1: Resolve Percent and Time key times.
            //

            index = 0;
            while (index < keyFrameCount)
            {
                KeyTime keyTime = _keyFrames[index].KeyTime;

                switch (keyTime.Type)
                {
                    case KeyTimeType.Percent:

                        _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.FromMilliseconds(
                            keyTime.Percent * calculationDuration.TotalMilliseconds);
                        index++;
                        break;

                    case KeyTimeType.TimeSpan:

                        _sortedResolvedKeyFrames[index]._resolvedKeyTime = keyTime.TimeSpan;

                        index++;
                        break;

                    case KeyTimeType.Paced:
                    case KeyTimeType.Uniform:

                        if (index == maxKeyFrameIndex)
                        {
                            // If the last key frame doesn't have a specific time
                            // associated with it its resolved key time will be
                            // set to the calculationDuration, which is the
                            // defined in the comments above where it is set. 
                            // Reason: We only want extra time at the end of the
                            // key frames if the user specifically states that
                            // the last key frame ends before the animation ends.

                            _sortedResolvedKeyFrames[index]._resolvedKeyTime = calculationDuration;
                            index++;
                        }
                        else if (   index == 0
                                 && keyTime.Type == KeyTimeType.Paced)
                        {
                            // Note: It's important that this block come after
                            // the previous if block because of rule precendence.

                            // If the first key frame in a multi-frame key frame
                            // collection is paced, we set its resolved key time
                            // to 0.0 for performance reasons.  If we didn't, the
                            // resolved key time list would be dependent on the
                            // base value which can change every animation frame
                            // in many cases.

                            _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.Zero;
                            index++;
                        }
                        else
                        {
                            if (keyTime.Type == KeyTimeType.Paced)
                            {
                                hasPacedKeyTimes = true;
                            }

                            KeyTimeBlock block = new KeyTimeBlock();
                            block.BeginIndex = index;

                            // NOTE: We don't want to go all the way up to the
                            // last frame because if it is Uniform or Paced its
                            // resolved key time will be set to the calculation 
                            // duration using the logic above.
                            //
                            // This is why the logic is:
                            //    ((++index) < maxKeyFrameIndex)
                            // instead of:
                            //    ((++index) < keyFrameCount)

                            while ((++index) < maxKeyFrameIndex)
                            {
                                KeyTimeType type = _keyFrames[index].KeyTime.Type;

                                if (   type == KeyTimeType.Percent
                                    || type == KeyTimeType.TimeSpan)
                                {
                                    break;
                                }   
                                else if (type == KeyTimeType.Paced)
                                {
                                    hasPacedKeyTimes = true;
                                }                                
                            }

                            Debug.Assert(index < keyFrameCount, 
                                "The end index for a block of unspecified key frames is out of bounds.");

                            block.EndIndex = index;
                            unspecifiedBlocks.Add(block);
                        }

                        break;
                }
            }

            //
            // Pass 2: Resolve Uniform key times.
            //

            for (int j = 0; j < unspecifiedBlocks.Count; j++)
            {
                KeyTimeBlock block = (KeyTimeBlock)unspecifiedBlocks[j];

                TimeSpan blockBeginTime = TimeSpan.Zero;

                if (block.BeginIndex > 0)
                {
                    blockBeginTime = _sortedResolvedKeyFrames[block.BeginIndex - 1]._resolvedKeyTime;
                }

                // The number of segments is equal to the number of key
                // frames we're working on plus 1.  Think about the case
                // where we're working on a single key frame.  There's a
                // segment before it and a segment after it.
                //
                //  Time known         Uniform           Time known
                //  ^                  ^                 ^
                //  |                  |                 |
                //  |   (segment 1)    |   (segment 2)   |

                Int64 segmentCount = (block.EndIndex - block.BeginIndex) + 1;
                TimeSpan uniformTimeStep = TimeSpan.FromTicks((_sortedResolvedKeyFrames[block.EndIndex]._resolvedKeyTime - blockBeginTime).Ticks / segmentCount);

                index = block.BeginIndex;
                TimeSpan resolvedTime = blockBeginTime + uniformTimeStep;

                while (index < block.EndIndex)
                {
                    _sortedResolvedKeyFrames[index]._resolvedKeyTime = resolvedTime;

                    resolvedTime += uniformTimeStep;
                    index++;
                }
            }

            //
            // Pass 3: Resolve Paced key times.
            //

            if (hasPacedKeyTimes)
            {
                ResolvePacedKeyTimes();
            }

            //
            // Sort resolved key frame entries.
            //

            Array.Sort(_sortedResolvedKeyFrames);

            _areKeyTimesValid = true;
            return;
        }
        private void ResolveKeyTimes()
        {
            Debug.Assert(!_areKeyTimesValid, "KeyFrameByteAnimaton.ResolveKeyTimes() shouldn't be called if the key times are already valid.");

            int keyFrameCount = 0;

            if (_keyFrames != null)
            {
                keyFrameCount = _keyFrames.Count;
            }

            if (keyFrameCount == 0)
            {
                _sortedResolvedKeyFrames = null;
                _areKeyTimesValid        = true;
                return;
            }

            _sortedResolvedKeyFrames = new ResolvedKeyFrameEntry[keyFrameCount];

            int index = 0;

            // Initialize the _originalKeyFrameIndex.
            for ( ; index < keyFrameCount; index++)
            {
                _sortedResolvedKeyFrames[index]._originalKeyFrameIndex = index;
            }

            // calculationDuration represents the time span we will use to resolve
            // percent key times. This is defined as the value in the following
            // precedence order:
            //   1. The animation's duration, but only if it is a time span, not auto or forever.
            //   2. The largest time span specified key time of all the key frames.
            //   3. 1 second, to match the From/To/By animations.

            TimeSpan calculationDuration = TimeSpan.Zero;

            Duration duration = Duration;

            if (duration.HasTimeSpan)
            {
                calculationDuration = duration.TimeSpan;
            }
            else
            {
                calculationDuration = LargestTimeSpanKeyTime;
            }

            int maxKeyFrameIndex = keyFrameCount - 1;
            List <KeyTimeBlock> unspecifiedBlocks = new List <KeyTimeBlock>();
            bool hasPacedKeyTimes = false;

            //
            // Pass 1: Resolve Percent and Time key times.
            //

            index = 0;
            while (index < keyFrameCount)
            {
                KeyTime keyTime = _keyFrames[index].KeyTime;

                switch (keyTime.Type)
                {
                case KeyTimeType.Percent:

                    _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.FromMilliseconds(
                        keyTime.Percent * calculationDuration.TotalMilliseconds);
                    index++;
                    break;

                case KeyTimeType.TimeSpan:

                    _sortedResolvedKeyFrames[index]._resolvedKeyTime = keyTime.TimeSpan;

                    index++;
                    break;

                case KeyTimeType.Paced:
                case KeyTimeType.Uniform:

                    if (index == maxKeyFrameIndex)
                    {
                        // If the last key frame doesn't have a specific time
                        // associated with it its resolved key time will be
                        // set to the calculationDuration, which is the
                        // defined in the comments above where it is set.
                        // Reason: We only want extra time at the end of the
                        // key frames if the user specifically states that
                        // the last key frame ends before the animation ends.

                        _sortedResolvedKeyFrames[index]._resolvedKeyTime = calculationDuration;
                        index++;
                    }
                    else if (index == 0 &&
                             keyTime.Type == KeyTimeType.Paced)
                    {
                        // Note: It's important that this block come after
                        // the previous if block because of rule precendence.

                        // If the first key frame in a multi-frame key frame
                        // collection is paced, we set its resolved key time
                        // to 0.0 for performance reasons.  If we didn't, the
                        // resolved key time list would be dependent on the
                        // base value which can change every animation frame
                        // in many cases.

                        _sortedResolvedKeyFrames[index]._resolvedKeyTime = TimeSpan.Zero;
                        index++;
                    }
                    else
                    {
                        if (keyTime.Type == KeyTimeType.Paced)
                        {
                            hasPacedKeyTimes = true;
                        }

                        KeyTimeBlock block = new KeyTimeBlock();
                        block.BeginIndex = index;

                        // NOTE: We don't want to go all the way up to the
                        // last frame because if it is Uniform or Paced its
                        // resolved key time will be set to the calculation
                        // duration using the logic above.
                        //
                        // This is why the logic is:
                        //    ((++index) < maxKeyFrameIndex)
                        // instead of:
                        //    ((++index) < keyFrameCount)

                        while ((++index) < maxKeyFrameIndex)
                        {
                            KeyTimeType type = _keyFrames[index].KeyTime.Type;

                            if (type == KeyTimeType.Percent ||
                                type == KeyTimeType.TimeSpan)
                            {
                                break;
                            }
                            else if (type == KeyTimeType.Paced)
                            {
                                hasPacedKeyTimes = true;
                            }
                        }

                        Debug.Assert(index < keyFrameCount,
                                     "The end index for a block of unspecified key frames is out of bounds.");

                        block.EndIndex = index;
                        unspecifiedBlocks.Add(block);
                    }

                    break;
                }
            }

            //
            // Pass 2: Resolve Uniform key times.
            //

            for (int j = 0; j < unspecifiedBlocks.Count; j++)
            {
                KeyTimeBlock block = unspecifiedBlocks[j];

                TimeSpan blockBeginTime = TimeSpan.Zero;

                if (block.BeginIndex > 0)
                {
                    blockBeginTime = _sortedResolvedKeyFrames[block.BeginIndex - 1]._resolvedKeyTime;
                }

                // The number of segments is equal to the number of key
                // frames we're working on plus 1.  Think about the case
                // where we're working on a single key frame.  There's a
                // segment before it and a segment after it.
                //
                //  Time known         Uniform           Time known
                //  ^                  ^                 ^
                //  |                  |                 |
                //  |   (segment 1)    |   (segment 2)   |

                Int64    segmentCount    = (block.EndIndex - block.BeginIndex) + 1;
                TimeSpan uniformTimeStep = TimeSpan.FromTicks((_sortedResolvedKeyFrames[block.EndIndex]._resolvedKeyTime - blockBeginTime).Ticks / segmentCount);

                index = block.BeginIndex;
                TimeSpan resolvedTime = blockBeginTime + uniformTimeStep;

                while (index < block.EndIndex)
                {
                    _sortedResolvedKeyFrames[index]._resolvedKeyTime = resolvedTime;

                    resolvedTime += uniformTimeStep;
                    index++;
                }
            }

            //
            // Pass 3: Resolve Paced key times.
            //

            if (hasPacedKeyTimes)
            {
                ResolvePacedKeyTimes();
            }

            //
            // Sort resolved key frame entries.
            //

            Array.Sort(_sortedResolvedKeyFrames);

            _areKeyTimesValid = true;
            return;
        }