File: System\Windows\Documents\FixedTextContainer.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:
//      Implements the FixedTextContainer
//
 
#pragma warning disable 1634, 1691 // To enable presharp warning disables (#pragma suppress) below.
 
namespace System.Windows.Documents
{
    using MS.Internal;
    using MS.Internal.Documents;
    using MS.Utility;
    using System.Windows;                // DependencyID etc.
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Markup;
    using System.Windows.Shapes;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Text;
    using System.Windows.Threading;              // Dispatcher
 
    //=====================================================================
    /// <summary>
    /// FixedTextContainer is an implementaiton of TextContainer for Fixed Documents
    /// </summary>
    internal sealed class FixedTextContainer : ITextContainer
    {
        //--------------------------------------------------------------------
        //
        // Connstructors
        //
        //---------------------------------------------------------------------
 
        #region Constructors
        /// <summary>
        ///    FixedTextContainer constructor that sets up the default backing store
        /// </summary>
        /// <param name="parent">
        ///    The object that will be parent of the top level content elements
        /// </param>
        internal FixedTextContainer(DependencyObject parent)
        {
            Debug.Assert(parent != null && parent is FixedDocument);
            _parent = parent;
            _CreateEmptyContainer();
        }
        #endregion Constructors
 
        //--------------------------------------------------------------------
        //
        // Public Methods
        //
        //---------------------------------------------------------------------
        #region Public Methods
 
        //
        // This is readonly Text OM. All modification methods returns false
        //
 
        void ITextContainer.BeginChange()
        {
        }
 
        /// <summary>
        /// <see cref="System.Windows.Documents.ITextContainer.BeginChangeNoUndo"/>
        /// </summary>
        void ITextContainer.BeginChangeNoUndo()
        {
            // We don't support undo, so follow the BeginChange codepath.
            ((ITextContainer)this).BeginChange();
        }
 
        /// <summary>
        /// </summary>
        void ITextContainer.EndChange()
        {
            ((ITextContainer)this).EndChange(false /* skipEvents */);
        }
 
        /// <summary>
        /// </summary>
        void ITextContainer.EndChange(bool skipEvents)
        {
        }
 
        // Allocate a new ITextPointer at the specified offset.
        // Equivalent to this.Start.CreatePointer(offset), but does not
        // necessarily allocate this.Start.
        ITextPointer ITextContainer.CreatePointerAtOffset(int offset, LogicalDirection direction)
        {
            return ((ITextContainer)this).Start.CreatePointer(offset, direction);
        }
 
        // Not Implemented.
        ITextPointer ITextContainer.CreatePointerAtCharOffset(int charOffset, LogicalDirection direction)
        {
            throw new NotImplementedException();
        }
 
        ITextPointer ITextContainer.CreateDynamicTextPointer(StaticTextPointer position, LogicalDirection direction)
        {
            return ((ITextPointer)position.Handle0).CreatePointer(direction);
        }
 
        StaticTextPointer ITextContainer.CreateStaticPointerAtOffset(int offset)
        {
            return new StaticTextPointer(this, ((ITextContainer)this).CreatePointerAtOffset(offset, LogicalDirection.Forward));
        }
 
        TextPointerContext ITextContainer.GetPointerContext(StaticTextPointer pointer, LogicalDirection direction)
        {
            return ((ITextPointer)pointer.Handle0).GetPointerContext(direction);
        }
 
        int ITextContainer.GetOffsetToPosition(StaticTextPointer position1, StaticTextPointer position2)
        {
            return ((ITextPointer)position1.Handle0).GetOffsetToPosition((ITextPointer)position2.Handle0);
        }
 
        int ITextContainer.GetTextInRun(StaticTextPointer position, LogicalDirection direction, char[] textBuffer, int startIndex, int count)
        {
            return ((ITextPointer)position.Handle0).GetTextInRun(direction, textBuffer, startIndex, count);
        }
 
        object ITextContainer.GetAdjacentElement(StaticTextPointer position, LogicalDirection direction)
        {
            return ((ITextPointer)position.Handle0).GetAdjacentElement(direction);
        }
 
        DependencyObject ITextContainer.GetParent(StaticTextPointer position)
        {
            return null;
        }
 
        StaticTextPointer ITextContainer.CreatePointer(StaticTextPointer position, int offset)
        {
            return new StaticTextPointer(this, ((ITextPointer)position.Handle0).CreatePointer(offset));
        }
 
        StaticTextPointer ITextContainer.GetNextContextPosition(StaticTextPointer position, LogicalDirection direction)
        {
            return new StaticTextPointer(this, ((ITextPointer)position.Handle0).GetNextContextPosition(direction));
        }
 
        int ITextContainer.CompareTo(StaticTextPointer position1, StaticTextPointer position2)
        {
            return ((ITextPointer)position1.Handle0).CompareTo((ITextPointer)position2.Handle0);
        }
 
        int ITextContainer.CompareTo(StaticTextPointer position1, ITextPointer position2)
        {
            return ((ITextPointer)position1.Handle0).CompareTo(position2);
        }
 
        object ITextContainer.GetValue(StaticTextPointer position, DependencyProperty formattingProperty)
        {
            return ((ITextPointer)position.Handle0).GetValue(formattingProperty);
        }
 
        #endregion Public Methods
 
        //--------------------------------------------------------------------
        //
        // Public Properties
        //
        //---------------------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// Specifies whether or not the content of this TextContainer may be
        /// modified.
        /// </summary>
        /// <value>
        /// True if content may be modified, false otherwise.
        /// </value>
        /// <remarks>
        /// This TextContainer implementation always returns true.
        /// </remarks>
        bool ITextContainer.IsReadOnly
        {
            get
            {
                return true;
            }
        }
 
        /// <summary>
        /// <see cref="TextContainer.Start"/>
        /// </summary>
        ITextPointer ITextContainer.Start
        {
            get
            {
                Debug.Assert(_start != null);
                return _start;
            }
        }
 
 
        /// <summary>
        /// <see cref="TextContainer.End"/>
        /// </summary>
        ITextPointer ITextContainer.End
        {
            get
            {
                Debug.Assert(_end != null);
                return _end;
            }
        }
 
        /// <summary>
        /// Autoincremented counter of content changes in this TextContainer
        /// </summary>
        uint ITextContainer.Generation
        {
            get
            {
                // For read-only content, return some constant value.
                return 0;
            }
        }
 
        /// <summary>
        /// Collection of highlights applied to TextContainer content.
        /// </summary>
        Highlights ITextContainer.Highlights
        {
            get
            {
                return this.Highlights;
            }
        }
 
        /// <summary>
        /// <see cref="TextContainer.Parent"/>
        /// </summary>
        DependencyObject ITextContainer.Parent
        {
            get { return _parent; }
        }
 
        // TextEditor owns setting and clearing this property inside its
        // ctor/OnDetach methods.
        ITextSelection ITextContainer.TextSelection
        {
            get { return this.TextSelection;}
            set { _textSelection = value; }
        }
 
        // Optional undo manager, always null for this ITextContainer.
        UndoManager ITextContainer.UndoManager { get { return null; } }
 
        // <see cref="System.Windows.Documents.ITextContainer/>
        ITextView ITextContainer.TextView
        {
            get
            {
                return _textview;
            }
 
            set
            {
                _textview = value;
            }
        }
 
        // Count of symbols in this tree, equivalent to this.Start.GetOffsetToPosition(this.End),
        // but doesn't necessarily allocate anything.
        int ITextContainer.SymbolCount
        {
            get
            {
                return ((ITextContainer)this).Start.GetOffsetToPosition(((ITextContainer)this).End);
            }
        }
 
        // Not implemented.
        int ITextContainer.IMECharCount
        {
            get
            {
                #pragma warning suppress 56503
                throw new NotImplementedException();
            }
        }
 
        #endregion Public Properties
 
        //--------------------------------------------------------------------
        //
        // Public Events
        //
        //---------------------------------------------------------------------
 
        #region Public Events
 
        public event EventHandler Changing { add { } remove { } }
 
        public event TextContainerChangeEventHandler Change { add { } remove { } }
 
        public event TextContainerChangedEventHandler Changed { add { } remove { } }
 
        #endregion Public Events
 
        //--------------------------------------------------------------------
        //
        // Internal Methods
        //
        //---------------------------------------------------------------------
 
        #region Internal Methods
 
        //--------------------------------------------------------------------
        // Utility Method
        //---------------------------------------------------------------------
        internal FixedTextPointer VerifyPosition(ITextPointer position)
        {
            ArgumentNullException.ThrowIfNull(position);
 
            if (position.TextContainer != this)
            {
                throw new ArgumentException(SR.Format(SR.NotInAssociatedContainer, "position"));
            }
 
            FixedTextPointer ftp = position as FixedTextPointer;
 
            if (ftp == null)
            {
                throw new ArgumentException(SR.Format(SR.BadFixedTextPosition, "position"));
            }
 
            return ftp;
        }
 
 
        internal int GetPageNumber(ITextPointer  textPointer)
        {
            FixedTextPointer fixedTextPointer = textPointer as FixedTextPointer;
            int  pageNumber = int.MaxValue;
 
            if (fixedTextPointer != null)
            {
                if (fixedTextPointer.CompareTo(((ITextContainer)this).Start) == 0)
                {
                    pageNumber = 0;
                }
                else if (fixedTextPointer.CompareTo(((ITextContainer)this).End) == 0)
                {
                    pageNumber = this.FixedDocument.PageCount - 1;
                }
                else
                {
                    FlowNode flowNode;
                    int flowOffset;
 
                    fixedTextPointer.FlowPosition.GetFlowNode(fixedTextPointer.LogicalDirection, out flowNode, out flowOffset);
 
                    FixedElement fixedElement = flowNode.Cookie as FixedElement;
 
                    if (flowNode.Type == FlowNodeType.Boundary)
                    {
                        if (flowNode.Fp > 0)
                        {
                            //Document end boundary node
                            pageNumber = this.FixedDocument.PageCount - 1;
                        }
                        else
                        {
                            //Document start boundary node
                            pageNumber = 0;
                        }
                    }
                    else if (flowNode.Type == FlowNodeType.Virtual || flowNode.Type == FlowNodeType.Noop)
                    {
                        pageNumber = (int)flowNode.Cookie;
                    }
                    else if (fixedElement != null)
                    {
                        pageNumber = (int)fixedElement.PageIndex;
                    }
                    else
                    {
                        FixedPosition fixPos;
                        bool res = FixedTextBuilder.GetFixedPosition(fixedTextPointer.FlowPosition, fixedTextPointer.LogicalDirection, out fixPos);
                        Debug.Assert(res);
                        if (res)
                        {
                            pageNumber = fixPos.Page;
                        }
                    }
                }
            }
 
            return pageNumber;
        }
 
        // Get the highlights, in Glyphs granularity, that covers this range
        internal void GetMultiHighlights(FixedTextPointer start, FixedTextPointer end, Dictionary<FixedPage, ArrayList> highlights, FixedHighlightType t,
            Brush foregroundBrush, Brush backgroundBrush)
        {
            Debug.Assert(highlights != null);
            if (start.CompareTo(end) > 0)
            {
                // make sure start <= end
                FixedTextPointer temp = start;
                start = end;
                end = temp;
            }
 
            FixedSOMElement[] elements;
            //Start and end indices in selection for first and last FixedSOMElements respectively
            int startIndex = 0;
            int endIndex = 0;
            if (_GetFixedNodesForFlowRange(start, end, out elements, out startIndex, out endIndex))
            {
                for(int i=0; i<elements.Length; i++)
                {
                    FixedSOMElement elem = elements[i];
                    
                    FixedNode fn = elem.FixedNode;
                    // Get the FixedPage if possible
                    FixedPage page = this.FixedDocument.SyncGetPageWithCheck(fn.Page);
                    if (page == null)
                    {
                        continue;
                    }
 
                    DependencyObject o = page.GetElement(fn);
                    if (o == null)
                    {
                        continue;
                    }
 
                    int beginOffset = 0;
                    int endOffset;
                    UIElement e;
                    if (o is Image || o is Path)
                    {
                        e = (UIElement)o;
                        endOffset = 1;
                    }
                    else
                    {
                        Glyphs g = o as Glyphs;
                        if (g == null)
                        {
                            continue;
                        }
                        e = (UIElement)o;
                        beginOffset = elem.StartIndex;
                        endOffset = elem.EndIndex;
                    }
 
                    if (i == 0)
                    {
                        beginOffset = startIndex;
                    }
 
                    if (i == elements.Length - 1)
                    {
                        endOffset = endIndex;
                    }
 
                    ArrayList lfs;
                    if (highlights.ContainsKey(page))
                    {
                        lfs = highlights[page];
                    }
                    else
                    {
                        lfs = new ArrayList();
                        highlights.Add(page, lfs);
                    }
 
                    FixedSOMTextRun textRun = elem as FixedSOMTextRun;
                    if (textRun != null && textRun.IsReversed)
                    {
                        int oldBeginOffset = beginOffset;
                        beginOffset = elem.EndIndex - endOffset;
                        endOffset = elem.EndIndex - oldBeginOffset;
                    }
 
                    FixedHighlight fh = new FixedHighlight(e, beginOffset, endOffset, t, foregroundBrush, backgroundBrush);
                    lfs.Add(fh);
                }
            }
        }
        #endregion Internal Methods
 
        //--------------------------------------------------------------------
        //
        // Internal Properties
        //
        //---------------------------------------------------------------------
 
        #region Internal Properties
 
        // Return the Fixed Document
        internal FixedDocument FixedDocument
        {
            get
            {
                if (_fixedPanel == null)
                {
                    if (_parent is FixedDocument)
                    {
                        _fixedPanel = (FixedDocument)_parent;
                    }
                }
 
                return _fixedPanel;
            }
        }
 
        internal FixedTextBuilder FixedTextBuilder
        {
            get
            {
                Debug.Assert(_fixedTextBuilder != null);
                return _fixedTextBuilder;
            }
        }
 
        internal FixedElement ContainerElement
        {
            get
            {
                Debug.Assert(_containerElement != null);
                return _containerElement;
            }
        }
 
        /// <summary>
        /// Collection of highlights applied to TextContainer content.
        /// </summary>
        internal Highlights Highlights
        {
            get
            {
                if (_highlights == null)
                {
                    _highlights = new Highlights(this);
                }
 
                return _highlights;
            }
        }
 
        // TextSelection associated with this container.
        internal ITextSelection TextSelection
        {
            get
            {
                return _textSelection;
            }
        }
 
        #endregion Internal Properties
 
        //--------------------------------------------------------------------
        //
        // Private Methods
        //
        //---------------------------------------------------------------------
 
        #region Private Methods
 
        //--------------------------------------------------------------------
        // Initilization
        //---------------------------------------------------------------------
        private void _CreateEmptyContainer()
        {
            // new text builder with map
            _fixedTextBuilder = new FixedTextBuilder(this);
 
            // create initial TextPointer and container element
            _start = new  FixedTextPointer(false, LogicalDirection.Backward, new FlowPosition(this, this.FixedTextBuilder.FixedFlowMap.FlowStartEdge, 1));
            _end = new FixedTextPointer(false, LogicalDirection.Forward, new FlowPosition(this, this.FixedTextBuilder.FixedFlowMap.FlowEndEdge, 0));
 
            _containerElement = new FixedElement(FixedElement.ElementType.Container, _start, _end, int.MaxValue);
            _start.FlowPosition.AttachElement(_containerElement);
            _end.FlowPosition.AttachElement(_containerElement);
        }
 
        internal void OnNewFlowElement(FixedElement parentElement, FixedElement.ElementType elementType, FlowPosition pStart, FlowPosition pEnd, Object source, int pageIndex)
        {
            FixedTextPointer eStart = new FixedTextPointer(false, LogicalDirection.Backward, pStart);
            FixedTextPointer eEnd = new FixedTextPointer(false, LogicalDirection.Forward, pEnd);
            FixedElement e = new FixedElement(elementType, eStart, eEnd, pageIndex);
            if (source != null)
            {
                e.Object = source;
            }
            // hook up logical tree
            parentElement.Append(e);
 
            // attach element to flownode for faster lookup later.
            pStart.AttachElement(e);
            pEnd.AttachElement(e);
        }
 
        //--------------------------------------------------------------------
        // TextContainer Element
        //---------------------------------------------------------------------
 
        // given a TextPointer range, find out all fixed position included in this range and
        // offset into the begin and end fixed element
        private bool _GetFixedNodesForFlowRange(ITextPointer start, ITextPointer end, out FixedSOMElement[] elements, out int startIndex, out int endIndex)
        {
            Debug.Assert(start.CompareTo(end) <= 0);
            elements  = null;
            startIndex = 0;
            endIndex = 0;
 
            if (start.CompareTo(end) == 0)
            {
                return false;
            }
 
            FixedTextPointer pStart    = (FixedTextPointer)start;
            FixedTextPointer pEnd      = (FixedTextPointer)end;
 
            return this.FixedTextBuilder.GetFixedNodesForFlowRange(pStart.FlowPosition, pEnd.FlowPosition, out elements, out startIndex, out endIndex);
        } //endofGetFixedNodes
        #endregion Private Methods
 
        //--------------------------------------------------------------------
        //
        // Private Fields
        //
        //---------------------------------------------------------------------
 
        #region Private Fields
        /// <summary>
        /// Cache for document content provider
        /// </summary>
        private FixedDocument          _fixedPanel;    // the fixed document
 
        /// <summary>
        /// Fixed To Flow Implemenation
        /// </summary>
        private FixedTextBuilder    _fixedTextBuilder; // heuristics to build text stream from fixed document
 
        /// <summary>
        ///  Text OM
        /// </summary>
        private DependencyObject  _parent;
        private FixedElement _containerElement;
        private FixedTextPointer _start;
        private FixedTextPointer _end;
 
        // Collection of highlights applied to TextContainer content.
        private Highlights _highlights;
        private ITextSelection _textSelection;
        // TextView associated with this TextContainer.
        private ITextView _textview;
 
        #endregion Private Fields
    }
}