File: System\Windows\Documents\FixedNode.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// 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.
 
//
// Description:
//      FixedNode is an immutable type that represents a fast and
//      efficient way to orderly locate an element in a fixed document
//      It is inserted into Fixed Order, which provides a linear view
//      of the fixed document.
//
 
namespace System.Windows.Documents
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Text;
 
    //=====================================================================
    /// <summary>
    /// FixedNode is an immutable type that represents a fast and
    /// efficient way to locate an element in a fixed document. All elements
    /// are leaf nodes of a fixed document tree.
    ///
    /// It is inserted into Fixed Order to provide a linear view
    /// of the fixed document.
    ///
    /// Usage:
    /// 1. Given a FixedNode, get the element it refers to (Glyphs/Image/etc. GetText etc.)
    /// 2. Given a Glyphs, construct its FixedNode representation (Page Analysis/HitTesting)
    /// 3. Given a FixedNode, find out which FlowNode it maps to.
    /// 4. Given a FixedNode, compare its relative position with another FixedNode in Fixed Order.
    /// 5. Used to represent artifical boundary (page/document) node.
    ///
    /// Design:
    ///
    /// FixedNode contains a path array that indicates the path from top most container
    /// to the leaf node. Example:
    ///
    /// +------------------+
    /// | P# | ChildIndex  |            1st Level Leaf Node
    /// +------------------+
    ///
    /// +--------------------------------+
    /// | P# | ChildIndex  | ChildIndex  |  2nd Level Leaf Node
    /// +--------------------------------+
    ///
    /// +---------------------------------------------+
    /// | P# | ChildIndex  | ChildIndex  | ChildIndex |  3rd Level Leaf Node
    /// +---------------------------------------------+
    ///
    /// etc.
    ///
    /// Although I have carefully considered alternative designs that does not
    /// require using a fixed array, that uses clever schema to fully utilize
    /// the entire uint32 address space, The Type Safty feature of CLR makes
    /// it difficult to efficiently handle overflow situation (deep nesting
    /// level leaf node).
    ///
    /// The Array approach has its virtue of simplicity.
    ///
    /// The real Page index always starts at 0, ends at n-1
    /// The real Index always starts at 0, ends at n-1
    /// Any index outside of the range is position mark.
    /// </summary>
    internal struct FixedNode : IComparable
    {
        //--------------------------------------------------------------------
        //
        // Static Method
        //
        //---------------------------------------------------------------------
 
        // Factory method to create a fixed node from its path
        // Avoiding create ArrayList if possible
        // Most common case childLevel is either 1, 2  level deep.
        // level 0 is always the page
        internal static FixedNode Create(int pageIndex, int childLevels, int level1Index, int level2Index, int[] childPath)
        {
            Debug.Assert(childLevels >= 1);
            switch (childLevels)
            {
                case 1:
                    return new FixedNode(pageIndex, level1Index);
 
                case 2:
                    return new FixedNode(pageIndex, level1Index, level2Index);
 
                default:
                    return FixedNode.Create(pageIndex, childPath);
            }
        }
 
        internal static FixedNode Create(int pageIndex, int[] childPath)
        {
            Debug.Assert(childPath != null);
            int[] completePath = new int[childPath.Length + 1];
            completePath[0] = pageIndex;
            childPath.CopyTo(completePath, 1);
 
            return new FixedNode(completePath);
        }
 
        //--------------------------------------------------------------------
        //
        // Connstructors
        //
        //---------------------------------------------------------------------
 
        #region Constructors
        /// <summary>
        /// Ctor for common case of Level 1 leaf node
        /// </summary>
        private FixedNode(int page, int level1Index)
        {
            _path = new int[2];
            _path[0] = page;
            _path[1] = level1Index;
        }
 
 
        // Ctor for common case Level 2 leaf node
        private FixedNode(int page, int level1Index, int level2Index)
        {
            _path = new int[3];
            _path[0] = page;
            _path[1] = level1Index;
            _path[2] = level2Index;
        }
 
        // Ctor for deep nesting case
        private FixedNode(int[] path)
        {
            Debug.Assert(path != null && path.Length >= 2); // At least a page index and an element index
            _path = path;
       }
        #endregion Constructors
 
        //--------------------------------------------------------------------
        //
        // Public Methods
        //
        //---------------------------------------------------------------------
 
        #region Public Methods
        // IComparable Override
        public int CompareTo(object o)
        {
            ArgumentNullException.ThrowIfNull(o);
 
            if (o.GetType() != typeof(FixedNode))
            {
                throw new ArgumentException(SR.Format(SR.UnexpectedParameterType, o.GetType(), typeof(FixedNode)), "o");
            }
 
            FixedNode fixedp = (FixedNode)o;
            return CompareTo(fixedp);
        }
 
 
        // Strongly typed compare function to avoid boxing
        // This function wil return 0 if two nodes are parent
        // and child relationship.
        // Positive means this node is before the input node.
        // Negative means this node is after the input node.
        public int CompareTo(FixedNode fixedNode)
        {
            int comp = this.Page.CompareTo(fixedNode.Page);
            if (comp == 0)
            {
                // compare index when two FixedNodes are in the same page
                int level = 1;
                while (level <= this.ChildLevels && level <= fixedNode.ChildLevels)
                {
                    int thisIndex   = this[level];
                    int fixedNodeIndex = fixedNode[level];
                    if (thisIndex == fixedNodeIndex)
                    {
                        level++;
                        continue;
                    }
                    return thisIndex.CompareTo(fixedNodeIndex);
                }
            }
            return comp;
        }
 
        /// <summary>
        /// childPath doesn't not include the PageNumberIndex
        /// This function wil return 0 if two nodes are parent
        /// and child relationship.
        /// Positive means this node is before the input node.
        /// Negative means this node is after the input node.
        /// </summary>
        /// <param name="childPath"></param>
        /// <returns></returns>
        internal int ComparetoIndex(int[] childPath)
        {
            for (int i = 0; i < childPath.Length && i < _path.Length - 1; i++)
            {
                if (childPath[i] == _path[i + 1])
                    continue;
 
                return childPath[i].CompareTo(_path[i + 1]);
            }
            return 0;
        }
 
        public static bool operator <(FixedNode fp1, FixedNode fp2)
        {
            return fp1.CompareTo(fp2) < 0;
        }
 
        public static bool operator <=(FixedNode fp1, FixedNode fp2)
        {
            return fp1.CompareTo(fp2) <= 0;
        }
 
        public static bool operator >(FixedNode fp1, FixedNode fp2)
        {
            return fp1.CompareTo(fp2) > 0;
        }
 
        public static bool operator >=(FixedNode fp1, FixedNode fp2)
        {
            return fp1.CompareTo(fp2) >= 0;
        }
 
 
        /// <summary>
        /// Compares this FixedNode with the passed in object
        /// </summary>
        /// <param name="o">The object to compare to "this"</param>
        /// <returns>bool - true if the FixedNodes are equal, false otherwise</returns>
        public override bool Equals(object o)
        {
            if (o is FixedNode)
            {
                return Equals((FixedNode)o);
            }
 
            return false;
        }
 
 
        // Strongly typed version of Equals to avoid boxing
        public bool Equals(FixedNode fixedp)
        {
            return (this.CompareTo(fixedp) == 0);
        }
 
 
        public static bool operator ==(FixedNode fp1, FixedNode fp2)
        {
            return fp1.Equals(fp2);
        }
 
        public static bool operator !=(FixedNode fp1, FixedNode fp2)
        {
            return !fp1.Equals(fp2);
        }
 
 
 
        /// <summary>
        /// Returns the HashCode for this FixedNode
        /// </summary>
        /// <returns>int the HashCode for this FixedNode</returns>
        public override int GetHashCode()
        {
            //return _path.GetHashCode();
			int hash = 0;
			foreach (int i in _path)
			{
				hash = 43 * hash + i;
			}
			return hash;
        }
 
#if DEBUG
        /// <summary>
        /// Create a string representation of this object
        /// </summary>
        /// <returns>string - A string representation of this object</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(CultureInfo.InvariantCulture, $"P{_path[0]}-");
            for (int i = 1; i < _path.Length; i++)
            {
                sb.Append(CultureInfo.InvariantCulture, $"[{_path[i]}]");
            }
            return sb.ToString();
        }
#endif
        #endregion Public Methods
 
        //--------------------------------------------------------------------
        //
        // Public Properties
        //
        //---------------------------------------------------------------------
 
        //--------------------------------------------------------------------
        //
        // Public Events
        //
        //---------------------------------------------------------------------
 
        //--------------------------------------------------------------------
        //
        // Internal Methods
        //
        //---------------------------------------------------------------------
 
        #region Internal Methods
        #endregion Internal Methods
 
        //--------------------------------------------------------------------
        //
        // Internal Properties
        //
        //---------------------------------------------------------------------
 
        #region Internal Properties
        internal int Page
        {
            get
            {
                return _path[0];
            }
        }
 
        // Accessor to different level of the path
        // Level 0 is always the page index.
        // element index inside the page start from Level 1.
        internal int this[int level]
        {
            get
            {
                Debug.Assert(level < _path.Length);
                return _path[level];
            }
        }
 
        // element path levels
        // it is always greater than 1.
        internal int ChildLevels
        {
            get
            {
                Debug.Assert(_path.Length >= 2);
                return _path.Length - 1;
            }
        }
 
        #endregion Internal Properties
 
        //--------------------------------------------------------------------
        //
        // Private Methods
        //
        //---------------------------------------------------------------------
 
        #region Private Methods
        #endregion Private Methods
 
        //--------------------------------------------------------------------
        //
        // Private Fields
        //
        //---------------------------------------------------------------------
 
        #region Private Fields
        private readonly int[] _path;   // path (including PageIndex) to the leaf node
        #endregion Private Fields
    }
}