File: System\Windows\Documents\FixedTextBuilder.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.
 
using MS.Internal.Documents;
using System.Windows.Controls;      // UIElementCollection
using System.Windows.Media;
using System.Windows.Automation;
using System.Windows.Documents.DocumentStructures;
using System.Collections;
using System.Globalization;
using System.Text;
using System.IO;
using System.Xml;
using Path = System.Windows.Shapes.Path;
 
using MS.Utility;
 
//
// Description:
//      FixedTextBuilder contains heuristics to map fixed document elements
//      into stream of flow text
//
 
namespace System.Windows.Documents
{
    //=====================================================================
    /// <summary>
    /// FixedTextBuilder contains heuristics to map fixed document elements
    /// into stream of flow text.
    /// </summary>
    internal sealed class FixedTextBuilder
    {
        //--------------------------------------------------------------------
        //
        // Consts
        //
        //---------------------------------------------------------------------
        #region Const
        internal const char FLOWORDER_SEPARATOR = ' ';
        #endregion Const
 
 
 
        //--------------------------------------------------------------------
        //
        // Statics
        //
        //---------------------------------------------------------------------
        #region Statics
 
        // A list of CultureInfo that are always adjacent
        internal static CultureInfo[] AdjacentLanguage =
        {
            new CultureInfo("zh-HANS"),     // Chinese Simplified
            new CultureInfo("zh-HANT"),     // Chinese Traditional
            new CultureInfo("zh-HK"),       // Chinese Hong Kong SAR
            new CultureInfo("zh-MO"),       // Chinese Macao SAR
            new CultureInfo("zh-CN"),       // Chinese China
            new CultureInfo("zh-SG"),       // Chinese Singapore
            new CultureInfo("zh-TW"),       // Chinese `Taiwan
            new CultureInfo("ja-JP"),       // Japanese
            new CultureInfo("ko-KR"),       // Korean
            new CultureInfo("th-TH")        // Thai
        };
 
 
        internal static bool AlwaysAdjacent(CultureInfo ci)
        {
            foreach (CultureInfo cultureInfo in AdjacentLanguage)
            {
                if (ci.Equals(cultureInfo))
                {
                    return true;
                }
            }
            return false;
        }
 
 
 
        // obtained via Unicode 3.0 book: C0 Controls and Basic Latin.
        //    0x002D/0x00AD and their related characters.
        //
        // NOTE it is okay to not getting accurate list as long as
        // we deal with common cases since this is used in heuristic
        // algorithm!
        internal static ReadOnlySpan<char> HyphenSet =>
        [
            '\x002D',     // Hyphen-Minus
            '\x2010',     // Hyphen
            '\x2011',     // Non-breaking Hyphen
            '\x2012',     // Figure-dash
            '\x2013',     // En-dash
            '\x2212',     // Minus Sign
            '\x00AD'      // Soft-Hyphen
        ];
 
        internal static bool IsHyphen(char target) => HyphenSet.Contains(target);
 
        // Space that was used in the heuristic algorithm.
        internal static bool IsSpace(char target)
        {
            // Only the regular space character is considered
            // space.
            return (target == '\x0020');
        }
        #endregion Statics
 
 
        //--------------------------------------------------------------------
        //
        // Connstructors
        //
        //---------------------------------------------------------------------
 
        #region Constructors
        internal FixedTextBuilder(FixedTextContainer container)
        {
            _container = container;
            _Init();
        }
        #endregion Constructors
 
        //--------------------------------------------------------------------
        //
        // Public Methods
        //
        //---------------------------------------------------------------------
 
        //--------------------------------------------------------------------
        //
        // Public Properties
        //
        //---------------------------------------------------------------------
 
        //--------------------------------------------------------------------
        //
        // Public Events
        //
        //---------------------------------------------------------------------
 
        //--------------------------------------------------------------------
        //
        // Internal Methods
        //
        //---------------------------------------------------------------------
 
        #region Internal Methods
 
        //--------------------------------------------------------------------
        // Virtualization
        //---------------------------------------------------------------------
 
        // Add virtual FlowNode to represent the page
        internal void AddVirtualPage()
        {
#if DEBUG
            DocumentsTrace.FixedTextOM.Builder.Trace($"AppendVirtualPage {_pageStructures.Count}");
#endif
            FixedPageStructure pageStructure = new FixedPageStructure(_pageStructures.Count);
#if DEBUG
            pageStructure.FixedTextBuilder = this;
#endif
            _pageStructures.Add(pageStructure);
 
            // Insert the virtural FlowNode into the Flow Order.
            _fixedFlowMap.FlowOrderInsertBefore(_fixedFlowMap.FlowEndEdge, pageStructure.FlowStart);
        }
 
 
        //--------------------------------------------------------------------
        // Devirtualizing flow structure
        //---------------------------------------------------------------------
 
        // Making sure a page is devirtualized
        // All function that takes FixedNode as parameter and asks for FlowPosition
        // related content should call this functions first!!!
        internal bool EnsureTextOMForPage(int pageIndex)
        {
            Debug.Assert(!_IsBoundaryPage(pageIndex));
            FixedPageStructure pageStructure = _pageStructures[pageIndex];
            if (!pageStructure.Loaded)
            {
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXEnsureOMBegin);
                try
                {
                    FixedPage page = _container.FixedDocument.SyncGetPage(pageIndex, false);
 
                    if (page == null)
                        return false;
 
                    Size pageSize = _container.FixedDocument.ComputePageSize(page);
 
                    page.Measure(pageSize);
                    page.Arrange(new Rect(new Point(0, 0), pageSize));
 
                    Debug.Assert(page != null);
                    Debug.Assert(page.IsInitialized);
 
                    bool constructSOM = true;
                    StoryFragments sf = page.GetPageStructure();
                    if (sf != null)
                    {
                        constructSOM = false;
                        FixedDSBuilder fb = new FixedDSBuilder(page, sf);
                        pageStructure.FixedDSBuilder = fb;
                    }
 
                    if (constructSOM)
                    {
                        FixedSOMPageConstructor pageConstructor = new FixedSOMPageConstructor(page, pageIndex);
                        pageStructure.PageConstructor = pageConstructor;
                        pageStructure.FixedSOMPage = pageConstructor.FixedSOMPage;
                    }
 
                    DocumentsTrace.FixedTextOM.Builder.Trace("_EnsureTextOMForPage Loading...");
                    _CreateFixedMappingAndElementForPage(pageStructure, page, constructSOM);
 
#if DEBUG
                    //
                    // Note: this debug verification is expensive. I have moved this code from FixedFlowMap.cs
                    // to reduce the rate is is called. Now it is called after each page is constructed and might
                    // still have effect on long documents and scrolling in page per stream case.
                    //
                    //FixedFlowMap.DebugVerifyMapping(true);
#endif
                }
                finally
                {
                    EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXEnsureOMEnd);
                }
 
                return true;
            }
 
#if DEBUG
            FixedPage fixedPage = _container.FixedDocument.SyncGetPage(pageIndex, false);
            fixedPage.FixedPageStructure = pageStructure;
#endif
            return false;
        }
 
        //--------------------------------------------------------------------
        // Fixed Document
        //---------------------------------------------------------------------
 
        // This could return null if FixedNode is an artificial boundary node
        internal FixedPage GetFixedPage(FixedNode node)
        {
            FixedDocument doc = _container.FixedDocument;
            return doc.SyncGetPageWithCheck(node.Page);
        }
 
        // This could return null if FixedNode is an artificial boundary node
        internal Glyphs GetGlyphsElement(FixedNode node)
        {
            FixedPage page = GetFixedPage(node);
            if (page != null)
            {
                return page.GetGlyphsElement(node);
            }
            return null;
        }
 
 
        //--------------------------------------------------------------------
        // Hit Testing / Keyboard Navigation
        //---------------------------------------------------------------------
 
        // Given a fixed node, find its next/previous line
        // count:
        //      in   # of lines desired
        //      out  # of lines remaining
        // Return null if no next line
        internal FixedNode[] GetNextLine(FixedNode currentNode, bool forward, ref int count)
        {
            if (_IsBoundaryPage(currentNode.Page))
            {
                return null;
            }
 
            EnsureTextOMForPage(currentNode.Page);
            FixedPageStructure ps = _pageStructures[currentNode.Page];
 
            if (_IsStartVisual(currentNode[1]))
            {
                FixedNode[] firstLine = ps.FirstLine;
                if (firstLine == null)
                {
                    return null;
                }
                currentNode = firstLine[0];
                count--;
            }
            else if (_IsEndVisual(currentNode[1]))
            {
                FixedNode[] lastLine = ps.LastLine;
                if (lastLine == null)
                {
                    return null;
                }
                currentNode = lastLine[0];
                count--;
            }
 
            FixedSOMTextRun run = _fixedFlowMap.MappingGetFixedSOMElement(currentNode, 0) as FixedSOMTextRun;
 
            if (run == null)
            {
                return null;
            }
 
            int lineIndex = run.LineIndex;
 
            return ps.GetNextLine(lineIndex, forward, ref count);
        }
 
 
        // Given a fixed node
        // Return null is cannot get line
        internal FixedNode[] GetLine(int pageIndex, Point pt)
        {
            EnsureTextOMForPage(pageIndex);
            return _pageStructures[pageIndex].FindSnapToLine(pt);
        }
 
        // Find first line on page
        internal FixedNode[] GetFirstLine(int pageIndex)
        {
            EnsureTextOMForPage(pageIndex);
            return _pageStructures[pageIndex].FirstLine;
        }
 
        //-----------------------------------------------
        // Fixed nodes ==>  FlowPosition
        //-----------------------------------------------
 
        // Find out flow offset given fixed position
        internal FlowPosition CreateFlowPosition(FixedPosition fixedPosition)
        {
            // make sure page has been loaded
            EnsureTextOMForPage(fixedPosition.Page);
 
            FixedSOMElement element = _fixedFlowMap.MappingGetFixedSOMElement(fixedPosition.Node, fixedPosition.Offset);
            if (element != null)
            {
                FlowNode flow = element.FlowNode;
                int fixedOffset = fixedPosition.Offset;
                FixedSOMTextRun run = element as FixedSOMTextRun;
                if (run != null && run.IsReversed)
                {
                    fixedOffset  = run.EndIndex - run.StartIndex - fixedOffset;
                }
                int offset = element.OffsetInFlowNode + fixedOffset - element.StartIndex;
                return new FlowPosition(_container, flow, offset);
            }
            return null;
        }
 
 
        internal FlowPosition GetPageStartFlowPosition(int pageIndex)
        {
            EnsureTextOMForPage(pageIndex);
 
            FlowNode fn = _pageStructures[pageIndex].FlowStart;
            // Remove this assert once we start supporting element across page bounary
            Debug.Assert(fn.Type == FlowNodeType.Start);
            return new FlowPosition(_container, fn, 0);
        }
 
 
        internal FlowPosition GetPageEndFlowPosition(int pageIndex)
        {
            EnsureTextOMForPage(pageIndex);
 
            // Remove this assert once we start supporting element across page bounary
            FlowNode fn = _pageStructures[pageIndex].FlowEnd;
            return new FlowPosition(_container, fn, 1);
        }
 
        //-----------------------------------------------
        // FlowPosition ==>  Fixed Nodes
        //-----------------------------------------------
 
        // This function can handle flow nodes that are mapped to fixed nodes (Object or Run type) and start and end nodes
        internal bool GetFixedPosition(FlowPosition position, LogicalDirection textdir, out FixedPosition fixedp)
        {
            // Currently broken on empty page and on start/end flow nodes!
            // Init out parameter
            fixedp = new FixedPosition(FixedFlowMap.FixedStartEdge, 0);
 
            int flowOffset;
            FlowNode flow;
 
            position.GetFlowNode(textdir, out flow, out flowOffset);
            FixedSOMElement[] fixes = flow.FixedSOMElements;
            if (fixes == null)
            {
                return false;
            }
 
            int loIndex = 0, hiIndex = fixes.Length - 1;
            while (hiIndex > loIndex)
            {
                int index = (hiIndex + loIndex + 1) >> 1; // middle, round up -- guarantee hiIndex >= index > loIndex
                if (fixes[index].OffsetInFlowNode > flowOffset)
                {
                    hiIndex = index - 1;
                }
                else
                {
                    loIndex = index;
                }
            }
 
            FixedSOMElement element = fixes[loIndex];
            FixedSOMTextRun run;
 
            if (loIndex > 0 &&
                textdir == LogicalDirection.Backward &&
                element.OffsetInFlowNode == flowOffset)
            {
                //check if we should really be in the prior element
                FixedSOMElement last = fixes[loIndex - 1];
                int offsetInLast = flowOffset - last.OffsetInFlowNode + last.StartIndex;
 
                if (offsetInLast == last.EndIndex)
                {
                    run = last as FixedSOMTextRun;
                    if (run != null && run.IsReversed)
                    {
                        offsetInLast = run.EndIndex - run.StartIndex - offsetInLast;
                    }
                    fixedp = new FixedPosition(last.FixedNode, offsetInLast);
                    return true;
                }
            }
 
            run = element as FixedSOMTextRun;
            int fixedOffset  = flowOffset - element.OffsetInFlowNode + element.StartIndex;
            if (run != null && run.IsReversed)
            {
                fixedOffset = run.EndIndex - run.StartIndex - fixedOffset;
            }
            fixedp = new FixedPosition(element.FixedNode, fixedOffset);
            return true;
        }
 
 
        // Find out fixed node given flow positions
        internal bool GetFixedNodesForFlowRange(FlowPosition pStart,
                                                            FlowPosition pEnd,
                                                            out FixedSOMElement[] somElements,
                                                            out int firstElementStart,
                                                            out int lastElementEnd)
        {
            Debug.Assert(pStart.CompareTo(pEnd) < 0);
            somElements = null;
            firstElementStart = 0;
            lastElementEnd = 0;
 
            FlowNode[] flowNodes;
            int charStart = 0;
            int charEnd = -1;
 
            int offsetStart;
            int offsetEnd;
 
            pStart.GetFlowNodes(pEnd, out flowNodes, out offsetStart, out offsetEnd);
            if (flowNodes.Length <= 0)
            {
                return false;
            }
 
            ArrayList ar = new ArrayList();
            FlowNode flowStart  = flowNodes[0];
            FlowNode flowEnd    = flowNodes[flowNodes.Length - 1];
 
            foreach (FlowNode flowScan in flowNodes)
            {
                int skipBefore = 0;
                int stopAt     = Int32.MaxValue;
                if (flowScan == flowStart)
                {
                    skipBefore = offsetStart;
                }
 
                if (flowScan == flowEnd)
                {
                    stopAt = offsetEnd;
                }
 
                if (flowScan.Type == FlowNodeType.Object)
                {
                    FixedSOMElement[] fixes = flowScan.FixedSOMElements;
                    Debug.Assert(fixes.Length == 1);
                    ar.Add(fixes[0]);
                }
 
                if (flowScan.Type == FlowNodeType.Run)
                {
                    FixedSOMElement[] fixes = flowScan.FixedSOMElements;
                    foreach (FixedSOMElement element in fixes)
                    {
                        int startIndex = element.OffsetInFlowNode;
                        if (startIndex >= stopAt)
                        {
                            break;
                        }
 
                        int endIndex = startIndex + element.EndIndex - element.StartIndex;
                        if (endIndex <= skipBefore)
                        {
                            continue;
                        }
 
                        ar.Add(element);
 
                        if (skipBefore >= startIndex && flowScan == flowStart)
                        {
                            charStart = element.StartIndex + skipBefore - startIndex;
                        }
 
                        if (stopAt <= endIndex && flowScan == flowEnd)
                        {
                            charEnd = element.StartIndex + stopAt - startIndex;
                            break;
                        }
                        else if (stopAt == endIndex + 1)
                        {
                            charEnd = element.EndIndex; //in case this ends at beginning of next node
                        }
                    }
                }//endofifrun
            }//endofforeach FlowNode
 
            // This array could be empty! (Maybe...)  Broken if it is.
            somElements = (FixedSOMElement[])ar.ToArray(typeof(FixedSOMElement));
            if (somElements.Length == 0)
            {
                return false;
            }
 
            if (flowStart.Type == FlowNodeType.Object)
            {   // Image
                firstElementStart = offsetStart;
            }
            else
            {
                firstElementStart = charStart;
            }
            if (flowEnd.Type == FlowNodeType.Object)
            {
                lastElementEnd = offsetEnd;
            }
            else
            {
                lastElementEnd = charEnd;
            }
 
            return true;
        }
 
        //-----------------------------------------------
        // FlowPosition ==>  Flow Content
        //-----------------------------------------------
 
        // Helper function to retrieve text from FixedNodes that are mapped
        // to a flow run represented by a FlowPosition.
        internal string GetFlowText(FlowNode flowNode)
        {
            Debug.Assert(flowNode.Type == FlowNodeType.Run);
 
            StringBuilder sb = new StringBuilder();
            FixedSOMElement[] fixes = flowNode.FixedSOMElements;
            Debug.Assert(flowNode.Type == FlowNodeType.Run);
            Debug.Assert(fixes != null);
 
            foreach (FixedSOMTextRun element in fixes)
            {
                sb.Append(element.Text);
            }
            return sb.ToString();
        }
 
        internal static bool MostlyRTL(string s)
        {
            int RTL = 0;
            int LTR = 0;
            foreach (char c in s)
            {
                if (_IsRTL(c))
                {
                    RTL++;
                }
                else if (c != ' ')
                {
                    LTR++;
                }
            }
 
            return (RTL > 0) && (LTR == 0 || (RTL / LTR >= 2));
        }
 
        //Decides whether two lines specified by line heights and a vertical distance between them is considered on the same line
        //verticalDistance needs to be passed as top of line2 - top of line 1
        internal static bool IsSameLine(double verticalDistance, double fontSize1, double fontSize2)
        {
            // According to spec for rubber band selection, to adjacent Glyphs are on same line if they have 50% overlap
            double smallSize = (fontSize1 < fontSize2) ? fontSize1 : fontSize2;
            double overlap = (verticalDistance > 0) ? (fontSize1 - verticalDistance) : (fontSize2 + verticalDistance);
            return overlap / smallSize > .5;
        }
 
        //------------------------------------------------------------------------------
        // Design Note (ZhenbinX - 2004/11/15) - Heuristic to test if two FixedSOMTextRun adjacent
        // in geometry for the purpose of adding additional separator into non-adjacent runs.
        // This algorithm bias toward NOT adding separator. The reason is artifically breaking
        // a word is more annoying than missing a space between words.
        //
        // The following steps are applied in order:
        // 1. If the two TextRun belong to two different languages, they are not
        //    adjacent. (A separator is needed)
        // 2. If the TextRun belongs to a language that does not use space as separator,
        //    they are always adjacent. (Need additional input about Bidi and complext
        //    script here. Currently NOT adjacent).
        // 3. If the two TextRuns belong to two different lines, they are NOT adjacent
        //    unless the end of the previous GlyphRun is HYPHEN.
        // 3. For TextRuns on the same line:
        //    a. If the last character of the previous run is SPACE, they are adjacent.
        //    b. If they are in different Bidi embedding level, they are NOT adjacent.
        //    c. If the distance between their InkBoundingBox is greater than 1/4 of
        //       the BoundingBox of the preceeding GlyphRun, they are NOT adjacent.
        //       (This can be tweaked to use step function in the future!).
        // All others are adjacent Glyphs!
        //
        //------------------------------------------------------------------------------
        internal static bool IsNonContiguous(CultureInfo ciPrev,
                                                    CultureInfo ciCurrent,
                                                    bool isSidewaysPrev,
                                                    bool isSidewaysCurrent,
                                                    string strPrev,
                                                    string strCurrent,
                                                    GlyphComparison comparison)
 
        {
            if (ciPrev != ciCurrent)
            {
                return true;
            }
            if (AlwaysAdjacent(ciPrev))
            {
                return false;
            }
 
 
            if (isSidewaysPrev != isSidewaysCurrent)
            {
                return true;  // not adjacent
            }
 
            if (strPrev.Length == 0 || strCurrent.Length == 0)
            {
                return false;  // adjacent
            }
 
            if (!isSidewaysPrev)
            {   // Horizontal Lines
                int textLength = strPrev.Length;
                char lastChar = strPrev[textLength - 1];
 
                if (IsSpace(lastChar))
                {
                    return false;       // adjacent
                }
 
                if (comparison == GlyphComparison.DifferentLine ||
                    comparison == GlyphComparison.Unknown)
                {
                    // Different Line
                    if (!IsHyphen(lastChar))
                    {
                        return true;   // non adjacent
                    }
                }
                else
                {
                    return comparison != GlyphComparison.Adjacent;
                }
            }
            return false;   // Default is adjacent
        }
 
 
        #endregion Internal Methods
 
 
        //--------------------------------------------------------------------
        //
        // Internal Properties
        //
        //---------------------------------------------------------------------
        #region Internal Properties
        internal FixedFlowMap FixedFlowMap
        {
            get
            {
                return _fixedFlowMap;
            }
        }
#if DEBUG
        internal FixedTextContainer FixedTextContainer
        {
            get
            {
                return _container;
            }
        }
#endif
 
        #endregion Internal Propeties
 
        //--------------------------------------------------------------------
        //
        // Private Methods
        //
        //---------------------------------------------------------------------
 
        #region Private Methods
 
        //--------------------------------------------------------------------
        // Initilization
        //---------------------------------------------------------------------
        private void _Init()
        {
            // Initialize Id
            _nextScopeId  = FixedFlowMap.FlowOrderScopeIdStart;
 
            // Create empty mapping
            _fixedFlowMap = new FixedFlowMap();
 
            //
            _pageStructures  = new List<FixedPageStructure>();
        }
 
        //--------------------------------------------------------------------
        // Heuristics
        //
        // Design Note (ZhenbinX):
        //      Page boundary is artificially represented by a pair
        //      of FixedNodes. Start of Page uses int.MinValue as
        //      its glyphs index, while End of Page uses int.MaxValue
        //      as its glyphs index.  All users of FixedNode needs
        //      to be aware of such articifical index and special case
        //      so that the "fake" glyphs indice are not used to access
        //      children.  One should always use FixedPage.GetGlyphs
        //      instead of using FixedPage's logical children collection
        //      directly, and should always check for possible null return.
        //
        //      The page boundaries, however, are mapped to a FlowNodeType.Run
        //      instead of any other FlowNodeType.  This is to be
        //      consistent with the general principal that non-run type
        //      FlowNode has no corresponding FixedNode.
        //
        //      Each FlowNode, if it is of type FlowNodeType.Run, contains
        //      a cookie that is the run length.
        //---------------------------------------------------------------------
 
        // Helper function for efficient creation of FixedNode used during pre-order walking.
        // current index is @ childIndex
        // depending on nesting level, the prefix can be hold at
        //      1. no prefix (level1)
        //      2. level1Index
        //      3. pathPrefix.
        //
        FixedNode _NewFixedNode(int pageIndex, int nestingLevel, int level1Index, int[] pathPrefix, int childIndex)
        {
            if (nestingLevel == 1)
            {
                return FixedNode.Create(pageIndex, nestingLevel, childIndex, -1, null);
            }
            else if (nestingLevel == 2)
            {
                return FixedNode.Create(pageIndex, nestingLevel, level1Index, childIndex, null);
            }
            else
            {
                // For deeply nested path, we need to append current childIndex to a pathPrefix
                // to form the entire childPath.
                Debug.Assert(pathPrefix != null);
                int[] newPath = new int[pathPrefix.Length + 1];
                pathPrefix.CopyTo(newPath, 0);
                newPath[newPath.Length - 1] = childIndex;
                return FixedNode.Create(pageIndex, nestingLevel, -1, -1, newPath);
            }
        }
 
 
        // Helper function to get Paths with ImageBrushes
        private bool _IsImage(object o)
        {
            System.Windows.Shapes.Path p = o as System.Windows.Shapes.Path;
            if (p != null)
            {
                return p.Fill is ImageBrush && p.Data != null;
            }
            return o is Image;
        }
 
 
        private bool _IsNonContiguous(FixedSOMTextRun prevRun, FixedSOMTextRun currentRun, GlyphComparison comparison)
        {
            Debug.Assert(prevRun != null);
            Debug.Assert(currentRun != null);
 
            if (prevRun.FixedNode == currentRun.FixedNode)
            {
                return currentRun.StartIndex != prevRun.EndIndex;
            }
 
            return IsNonContiguous(prevRun.CultureInfo,
                                    currentRun.CultureInfo,
                                    prevRun.IsSideways,
                                    currentRun.IsSideways,
                                    prevRun.Text,
                                    currentRun.Text,
                                    comparison);
        }
 
        private GlyphComparison _CompareGlyphs(Glyphs glyph1, Glyphs glyph2)
        {
            GlyphComparison comparison = GlyphComparison.DifferentLine;
            if (glyph1 == glyph2)
            {
                comparison = GlyphComparison.SameLine;
            }
            else if (glyph1 != null && glyph2 != null)
            {
                GlyphRun run1 = glyph1.ToGlyphRun();
                GlyphRun run2 = glyph2.ToGlyphRun();
                if (run1 != null && run2 != null)
                {
                    Rect box1 = run1.ComputeAlignmentBox();
                    box1.Offset(glyph1.OriginX, glyph1.OriginY);
                    Rect box2 = run2.ComputeAlignmentBox();
                    box2.Offset(glyph2.OriginX, glyph2.OriginY);
 
                    bool LTR1 = ((glyph1.BidiLevel & 1) == 0);
                    bool LTR2 = ((glyph2.BidiLevel & 1) == 0);
 
                    GeneralTransform transform = glyph2.TransformToVisual(glyph1);
                    Point prevPt = LTR1 ? box1.TopRight : box1.TopLeft;
                    Point currentPt = LTR2 ? box2.TopLeft : box2.TopRight;
                    if (transform != null)
                    {
                        transform.TryTransform(currentPt, out currentPt);
                    }
 
                    if (IsSameLine(currentPt.Y - prevPt.Y, box1.Height, box2.Height))
                    {
                        comparison = GlyphComparison.SameLine;
                        if (LTR1 == LTR2)
                        {
                            double xDistance = Math.Abs(currentPt.X - prevPt.X);
                            double maxHeight = Math.Max(box1.Height, box2.Height);
 
                            if (xDistance / maxHeight < .05)
                            {
                                comparison = GlyphComparison.Adjacent;
                            }
                        }
                    }
                }
            }
            return comparison;
        }
 
        // Apply heuristics
        // Create FlowNode structure
        // Create Fixed to Flow mapping
        // Create FixedElement
        private void _CreateFixedMappingAndElementForPage(FixedPageStructure pageStructure, FixedPage page, bool constructSOM)
        {
            List<FixedNode> fixedNodes = new List<FixedNode>();
            _GetFixedNodes(pageStructure,
                           page.Children,
                           1,
                           -1,
                           null,
                           constructSOM,
                           fixedNodes,
                           Matrix.Identity);
 
            FlowModelBuilder flowBuilder = new FlowModelBuilder(this, pageStructure, page);
            flowBuilder.FindHyperlinkPaths(page);
 
            if (constructSOM)
            {
                pageStructure.FixedSOMPage.MarkupOrder = fixedNodes;
                pageStructure.ConstructFixedSOMPage(fixedNodes);
 
 
                _CreateFlowNodes(pageStructure.FixedSOMPage, flowBuilder);
                pageStructure.PageConstructor = null;
            }
            else
            {
                pageStructure.FixedDSBuilder.ConstructFlowNodes(flowBuilder, fixedNodes);
            }
 
            flowBuilder.FinishMapping();
 
#if DEBUG
            pageStructure.FixedNodes = fixedNodes;
            //flowBuilder.DumpToFile("FlowDump_Page_" + pageStructure.PageIndex.ToString() + ".xml");
#endif
        }
 
        private void _GetFixedNodes(
                                    FixedPageStructure pageStructure,
                                    IEnumerable oneLevel,
                                    int nestingLevel,       // the level of nesting
                                    int level1Index,        // if nesting level == 2, this is leve1 1 index
                                    int[] pathPrefix,   // if nesting level > 2, this is used to represent prefix, otherwise it is null
                                    bool constructLines,
                                    List<FixedNode> fixedNodes, // start empty, nodes will be added to this list
                                    Matrix transform
                                    )
        {
            int pageIndex = pageStructure.PageIndex;
 
            DocumentsTrace.FixedTextOM.Builder.Trace($"_FlowOrderAnalysis P{pageIndex}-L[{pageIndex}]");
 
            int currentScopeId = _NewScopeId();
 
            // Create per run fixed nodes
            IFrameworkInputElement namedNode;
 
            int childIndex = 0;
            IEnumerator elements = oneLevel.GetEnumerator();
            while (elements.MoveNext())
            {
                if (!constructLines)
                {
                    namedNode = elements.Current as IFrameworkInputElement;
                    if (namedNode != null && namedNode.Name != null && namedNode.Name.Length != 0)
                    {
                        pageStructure.FixedDSBuilder.BuildNameHashTable(namedNode.Name,
                                    elements.Current as UIElement,
                                    fixedNodes.Count);
                    }
                }
 
                if (_IsImage(elements.Current) ||
                    (elements.Current is Glyphs && (elements.Current as Glyphs).MeasurementGlyphRun != null))
                {
                    fixedNodes.Add(
                        _NewFixedNode(pageIndex, nestingLevel, level1Index, pathPrefix, childIndex)
                                            );
                    // GlyphRuns are non contiguous due to Image
                }
                else if (constructLines && elements.Current is Path)
                {
                    pageStructure.PageConstructor.ProcessPath(elements.Current as Path, transform);
                }
                else if (elements.Current is Canvas)
                {
                    Transform localTransform = Transform.Identity;
 
                    // Drill down to next level
                    IEnumerable children;
 
                    Canvas canvas = elements.Current as Canvas;
 
                    children       = canvas.Children;
                    localTransform = canvas.RenderTransform;
 
                    if (localTransform == null)
                    {
                        localTransform = Transform.Identity;
                    }
 
                    if (children != null)
                    {
                        int[] newPathPrefix = null;
                        if (nestingLevel >= 2)
                        {
                            // anything more than two level deep, we need to use path array
                            if (nestingLevel == 2)
                            {
                                Debug.Assert(pathPrefix == null);
                                Debug.Assert(level1Index >= 0);
                                newPathPrefix = new int[2];
                                newPathPrefix[0] = level1Index;
                            }
                            else
                            {
                                newPathPrefix = new int[pathPrefix.Length + 1];
                                pathPrefix.CopyTo(newPathPrefix, 0);
                            }
                            // Append the childIndex to newPathPrefix.
                            Debug.Assert(newPathPrefix != null);
                            newPathPrefix[newPathPrefix.Length - 1] = childIndex;
                        }
#if DEBUG
                        else
                        {
                            Debug.Assert(pathPrefix == null && newPathPrefix == null);
                        }
#endif
                        _GetFixedNodes(
                                pageStructure,
                                children,
                                (nestingLevel + 1), // go to next nesting level
                                (nestingLevel == 1 ? childIndex : -1), // use this index if we are going from 1 to 2
                                newPathPrefix,         // otherwise use this path prefix
                                constructLines,
                                fixedNodes,
                                localTransform.Value * transform
                                );
                    }//endofElementIsCanvas
                }
 
                childIndex++;
            }
        }
 
        private void _CreateFlowNodes(FixedSOMPage somPage, FlowModelBuilder flowBuilder)
        {
            flowBuilder.AddStartNode(FixedElement.ElementType.Section);
            somPage.SetRTFProperties(flowBuilder.FixedElement);
 
            foreach (FixedSOMContainer container in somPage.SemanticBoxes)
            {
                _CreateFlowNodes(container, flowBuilder);
            }
 
            //Add the remaining hyperlinks at the end of the page
            flowBuilder.AddLeftoverHyperlinks();
 
            flowBuilder.AddEndNode();
        }
 
 
 
 
        //We will need to have a special case for an empty page
        private void _CreateFlowNodes(FixedSOMContainer node, FlowModelBuilder flowBuilder)
        {
            FixedElement.ElementType[] elementsForNode = node.ElementTypes;
            foreach (FixedElement.ElementType type in elementsForNode)
            {
                flowBuilder.AddStartNode(type);
                node.SetRTFProperties(flowBuilder.FixedElement);
            }
 
 
            List<FixedSOMSemanticBox> children = node.SemanticBoxes;
 
            foreach (FixedSOMSemanticBox box in children)
            {
                if (box is FixedSOMElement)
                {
                    flowBuilder.AddElement((FixedSOMElement)box);
                }
                else if (box is FixedSOMContainer)
                {
                    _CreateFlowNodes((FixedSOMContainer)box, flowBuilder);
                }
                else
                {
                    Debug.Assert(false);
                }
            }
 
            foreach (FixedElement.ElementType type in elementsForNode)
            {
                flowBuilder.AddEndNode();
            }
        }
 
 
 
        //--------------------------------------------------------------------
        // Fixed Document
        //---------------------------------------------------------------------
 
        private bool _IsStartVisual(int visualIndex)
        {
            return visualIndex == FixedFlowMap.FixedOrderStartVisual;
        }
 
        private bool _IsEndVisual(int visualIndex)
        {
            return visualIndex == FixedFlowMap.FixedOrderEndVisual;
        }
 
        private bool _IsBoundaryPage(int pageIndex)
        {
            return (   pageIndex == FixedFlowMap.FixedOrderStartPage
                    || pageIndex == FixedFlowMap.FixedOrderEndPage
                    );
        }
 
        // Advance the scope ID -- called when a new flow scope is needed.
        // the scope ID is used to identify the flow scope in flow order.
        //
        // For instance:
        //  <Start Id=1> <TextRun Id=1> <Image Id=2> <TextRun Id=1> <TextRun Id=1> <End Id=1>
        // All Id =1 consists of one scope while Id=2 opens a new scope (in this case a nested scope).
        //
        private int _NewScopeId()
        {
            return _nextScopeId++;
        }
 
 
        private static bool _IsRTL(char c)
        {
            /*return (c > 0x590 && c < 0x780) ||
                (c >= 0xFB1D && c <= 0xFDFD) ||
                (c >= 0xFE70 && c <= 0xFEFC);*/
            return (c >= 0x5D0 && c <= 0x60B) ||
                (c == 0x60D) ||
                (c >= 0x61B && c <= 0x64A) ||
                (c >= 0x66D && c <= 0x6D5 && c != 0x670) ||
                (c == 0x6DD) || (c == 0x6E5) || (c == 0x6E6) ||
                (c == 0x6EE) || (c == 0x6EF) ||
                (c >= 0x6FA && c <= 0x70D) ||
                (c == 0x710) || (c >= 0x712 && c <= 0x72F) ||
                (c >= 0x74D && c <= 0x7A5) || (c == 0x7B1) ||
                (c == 0xFB1D) || (c >= 0xFB1F && c <= 0xFD3D && c != 0xFB29) ||
                (c >= 0xFD50 && c <= 0xFDFC) || (c >= 0xFE70 && c <= 0xFEFC);
        }
 
        #endregion Private Methods
 
        //--------------------------------------------------------------------
        //
        // Private Properties
        //
        //---------------------------------------------------------------------
 
        #region Private Properties
        #endregion Private Properties
 
        //--------------------------------------------------------------------
        //
        // Private Fields
        //
        //---------------------------------------------------------------------
 
        #region Private Fields
        private readonly FixedTextContainer _container;     // page container
        private List<FixedPageStructure>  _pageStructures; // contain all FixedPageStructure for each page.
        private int _nextScopeId;                           // Next FlowNode ScopeId
        private FixedFlowMap _fixedFlowMap;
        private static bool[] _cTable = new bool[] { true, //0x5BF   //Combining marks table
                false, true, true, false, // 0x5C0 - 3
                true, true, false, true, //0x5C4
                true, true, //0x6D6 - 7
                true, true, true, true, //0x6D8 - B
                true, false, false, true, //C - F
                true, true, true, true, //0x6E0 - 3
                true, false, false, true, //0x6E4 - 7
                true, false, true, true, //0x6E8 - B
                true, true //C, D
            };
        #endregion Private Fields
 
        //--------------------------------------------------------------------
        //
        // Private Class
        //
        //--------------------------------------------------------------------
 
        #region Private Class
        internal sealed class FlowModelBuilder
        {
            private sealed class LogicalHyperlink
            {
                public LogicalHyperlink(Uri uri, Geometry geom, UIElement uiElement)
                {
                    _uiElement = uiElement;
                    _uri = uri;
                    _geometry = geom;
                    _boundingRect = geom.Bounds; //Get bounding rect
                    _used = false;
                }
 
                public Uri Uri
                {
                    get
                    {
                        return _uri;
                    }
                }
 
                public Geometry Geometry
                {
                    get
                    {
                        return _geometry;
                    }
                }
 
                public Rect BoundingRect
                {
                    get
                    {
                        return _boundingRect;
                    }
                }
 
                public UIElement UIElement
                {
                    get
                    {
                        return _uiElement;
                    }
                }
 
                public bool Used
                {
                    get
                    {
                        return _used;
                    }
                    set
                    {
                        _used = value;
                    }
                }
 
 
                private UIElement _uiElement;
                private Uri _uri;
                private Geometry _geometry;
                private Rect _boundingRect;
                private bool _used;
            }
 
            private sealed class LogicalHyperlinkContainer : IEnumerable<LogicalHyperlink>
            {
                public LogicalHyperlinkContainer()
                {
                    _hyperlinks = new List<LogicalHyperlink>();
                }
 
                IEnumerator<LogicalHyperlink> IEnumerable<LogicalHyperlink>.GetEnumerator()
                {
                    return _hyperlinks.GetEnumerator();
                }
 
                IEnumerator IEnumerable.GetEnumerator()
                {
                    return _hyperlinks.GetEnumerator();
                }
 
                public void AddLogicalHyperlink(Uri uri, Geometry geometry, UIElement uiElement)
                {
                    LogicalHyperlink hyperlink = new LogicalHyperlink(uri, geometry, uiElement);
                    _hyperlinks.Add(hyperlink);
                }
 
                //Returns a Uri that is associated with a SOMElement if there is one
                //Also returns the actual UIElement as an output variable if a Path with NavigateUri
                //is used to make this SOMElement look like an actual hyperlink
                public Uri GetUri(FixedSOMElement element, FixedPage p, out UIElement shadowElement)
                {
                    shadowElement = null;
                    UIElement e = p.GetElement(element.FixedNode) as UIElement;
                    if (e == null)
                    {
                        Debug.Assert(false);
                        return null;
                    }
                    LogicalHyperlink logicalHyperlink = null;
                    Uri relUri = FixedPage.GetNavigateUri(e);
                    if (relUri == null && _hyperlinks.Count > 0)
                    {
                        Transform t = e.TransformToAncestor(p) as Transform;
                        Geometry g;
                        if (e is Glyphs)
                        {
                            GlyphRun run = ((Glyphs)e).ToGlyphRun();
                            Rect designRect = run.ComputeAlignmentBox();
                            designRect.Offset(run.BaselineOrigin.X, run.BaselineOrigin.Y);
 
                            g = new RectangleGeometry(designRect);
                        }
                        else if (e is Path)
                        {
                            g = ((Path)e).Data;
                        }
                        else
                        {
                            Debug.Assert(e is Image);
                            Image im = (Image)e;
                            g = new RectangleGeometry(new Rect(0, 0, im.Width, im.Height));
                        }
                        logicalHyperlink = _GetHyperlinkFromGeometry(g, t);
                        if (logicalHyperlink != null)
                        {
                            relUri = logicalHyperlink.Uri;
                            shadowElement = logicalHyperlink.UIElement;
                        }
                    }
                    if (relUri == null)
                    {
                        return null;
                    }
 
                    return FixedPage.GetLinkUri(p, relUri);
                }
 
 
                //Marks the hyperlink associated with a specific UIElement as "used"
                public void MarkAsUsed(UIElement uiElement)
                {
                    for (int i=0; i<_hyperlinks.Count; i++)
                    {
                        LogicalHyperlink hyperlink = _hyperlinks[i];
                        if (hyperlink.UIElement == uiElement)
                        {
                            hyperlink.Used = true;
                            break;
                        }
                    }
                }
 
                private LogicalHyperlink _GetHyperlinkFromGeometry(Geometry geom, Transform t)
                {
                    Geometry g = geom;
 
                    if (t != null && !t.Value.IsIdentity)
                    {
                        g = PathGeometry.CreateFromGeometry(geom);
                        g.Transform = t;
                    }
 
                    double minArea = g.GetArea() * .99;
                    Rect r = g.Bounds;
 
                    for (int i = 0; i < _hyperlinks.Count; i++)
                    {
                        // do fast rect overlap before doing more expensive geometric comparison
                        if (r.IntersectsWith(_hyperlinks[i].BoundingRect))
                        {
                            Geometry combined = Geometry.Combine(g, _hyperlinks[i].Geometry, GeometryCombineMode.Intersect, Transform.Identity);
                            if (combined.GetArea() > minArea)
                            {
                                return _hyperlinks[i];
                            }
                        }
                    }
 
                    return null;
                }
 
                private List<LogicalHyperlink> _hyperlinks;
            }
 
            public FlowModelBuilder(FixedTextBuilder builder, FixedPageStructure pageStructure, FixedPage page)
            {
                _builder = builder;
                _container = builder._container;
                _pageIndex = pageStructure.PageIndex;
                _textRuns = new List<FixedSOMTextRun>();
                _flowNodes = new List<FlowNode>();
                _fixedNodes = new List<FixedNode>();
                _nodesInLine = new List<FixedNode>();
                _lineResults = new List<FixedLineResult>();
                _endNodes = new Stack();
                _fixedElements = new Stack();
                _mapping = builder._fixedFlowMap;
                _pageStructure = pageStructure;
                _currentFixedElement = _container.ContainerElement;
                _lineLayoutBox = Rect.Empty;
                _logicalHyperlinkContainer = new LogicalHyperlinkContainer();
                _fixedPage = page;
#if DEBUG
                _dumpDoc = new XmlDocument();
                _currentDumpNode = _dumpDoc.CreateElement("FlowModel");
                _dumpDoc.AppendChild(_currentDumpNode);
#endif
 
            }
 
 
            public void FindHyperlinkPaths(FrameworkElement elem)
            {
                //we are only interested in hyperlinks created by putting Path on top of a Gyphs element
 
                Debug.Assert(elem is FixedPage || elem is Canvas);
                IEnumerable children = LogicalTreeHelper.GetChildren(elem);
 
                foreach (UIElement child in children)
                {
                    Canvas canvas = child as Canvas;
                    if (canvas != null)
                    {
                        FindHyperlinkPaths(canvas);
                    }
 
                    if (!(child is Path) || ((Path)child).Fill is ImageBrush)
                    {
                        // ignore these, these are content
                        continue;
                    }
 
                    Uri navUri = FixedPage.GetNavigateUri(child);
 
                    if (navUri != null && ((Path)child).Data != null)
                    {
                        Transform trans = child.TransformToAncestor(_fixedPage) as Transform;
 
                        Geometry geom = ((Path)child).Data;
                        if (trans != null && !trans.Value.IsIdentity)
                        {
                            geom = PathGeometry.CreateFromGeometry(geom);
                            geom.Transform = trans;
                        }
                        _logicalHyperlinkContainer.AddLogicalHyperlink(navUri, geom, child);
                    }
                }
            }
 
            public void AddLeftoverHyperlinks()
            {
                foreach (LogicalHyperlink hyperlink in _logicalHyperlinkContainer)
                {
                    if (!hyperlink.Used)
                    {
                        _AddStartNode(FixedElement.ElementType.Paragraph);
                        _AddStartNode(FixedElement.ElementType.Hyperlink);
                        _currentFixedElement.SetValue(Hyperlink.NavigateUriProperty, hyperlink.Uri);
                        _currentFixedElement.SetValue(FixedElement.HelpTextProperty, (String) (hyperlink.UIElement.GetValue(AutomationProperties.HelpTextProperty)));
                        _currentFixedElement.SetValue(FixedElement.NameProperty, (String) (hyperlink.UIElement.GetValue(AutomationProperties.NameProperty)));
                        _AddEndNode();
                        _AddEndNode();
                    }
                }
            }
            //Use this for start tags
            public void AddStartNode(FixedElement.ElementType type)
            {
                _FinishTextRun(true);
                _FinishHyperlink();
                _AddStartNode(type);
            }
 
            public void AddEndNode()
            {
                _FinishTextRun(false);
                _FinishHyperlink();
                _AddEndNode();
            }
            //Use this for images and text runs -- generates necessary flow nodes
            public void AddElement(FixedSOMElement element)
            {
                FixedPage page = _builder.GetFixedPage(element.FixedNode);
                UIElement shadowHyperlink;
                Uri navUri = _logicalHyperlinkContainer.GetUri(element, page, out shadowHyperlink);
                if (element is FixedSOMTextRun)
                {
                    // Will add code to get font info for rich copy here
                    FixedSOMTextRun run = element as FixedSOMTextRun;
                    bool createNewRun = (_currentRun == null) || (!run.HasSameRichProperties(_currentRun))
                        || navUri != _currentNavUri || (navUri != null && navUri.ToString() != _currentNavUri.ToString());
 
                    if (createNewRun)
                    {
                        if (_currentRun != null)
                        {
                            //Close existing inline tag
                            FixedSOMFixedBlock parent = run.FixedBlock;
 
                            FixedSOMTextRun lastRun = _textRuns[_textRuns.Count - 1];
 
                            Glyphs currentRunGlyph = _builder.GetGlyphsElement(lastRun.FixedNode);
                            Glyphs glyphs = _builder.GetGlyphsElement(run.FixedNode);
 
                            GlyphComparison comparison = _builder._CompareGlyphs(currentRunGlyph, glyphs);
 
                            bool addSpace = false;
                            if (_builder._IsNonContiguous(lastRun, run, comparison))
                            {
                                addSpace = true;
                            }
 
                            _FinishTextRun(addSpace);
                        }
 
                        _SetHyperlink(navUri, run.FixedNode, shadowHyperlink);
                        //Open new Run tag and set RTF props
 
                        _AddStartNode(FixedElement.ElementType.Run);
                        run.SetRTFProperties(_currentFixedElement);
                        _currentRun = run;
                    }
 
                    _textRuns.Add((FixedSOMTextRun)element);
                    if (_fixedNodes.Count == 0 || _fixedNodes[_fixedNodes.Count - 1] != element.FixedNode)
                    {
                        _fixedNodes.Add(element.FixedNode);
                    }
                }
                else if (element is FixedSOMImage)
                {
                    FixedSOMImage image = (FixedSOMImage)element;
                    _FinishTextRun(true);
                    _SetHyperlink(navUri, image.FixedNode, shadowHyperlink);
 
                    _AddStartNode(FixedElement.ElementType.InlineUIContainer);
 
                    FlowNode flowImageNode = new FlowNode(_NewScopeId(), FlowNodeType.Object, null);
                    // Create a new FixedElement to represent this new node.
                    _container.OnNewFlowElement(_currentFixedElement,
                                    FixedElement.ElementType.Object,
                                    new FlowPosition(_container, flowImageNode, 0),
                                    new FlowPosition(_container, flowImageNode, 1),
                                    image.Source,
                                    _pageIndex
                                    );
 
 
                    _flowNodes.Add(flowImageNode);
 
                    // Do fixed/flow mapping for image
                    element.FlowNode = flowImageNode;
                    flowImageNode.FixedSOMElements = new FixedSOMElement[] { element };
                    _mapping.AddFixedElement(element);
                    // Do we need this?
                    _fixedNodes.Add(element.FixedNode);
 
                    //copy automation properties
                    FixedElement fElement = (FixedElement)flowImageNode.Cookie;
                    fElement.SetValue(FixedElement.NameProperty, image.Name);
                    fElement.SetValue(FixedElement.HelpTextProperty, image.HelpText);
 
                    _AddEndNode();
                }
                else
                {
                    Debug.Assert(false);
                }
            }
 
            public void FinishMapping()
            {
                _FinishLine();
                _mapping.MappingReplace(_pageStructure.FlowStart, _flowNodes);
                _pageStructure.SetFlowBoundary(_flowNodes[0], _flowNodes[_flowNodes.Count-1]);
                _pageStructure.SetupLineResults(_lineResults.ToArray());
            }
 
#if DEBUG
            public void DumpToFile(string file)
            {
                //_dumpDoc.Save(file);
                StringWriter writer = new StringWriter();
                _dumpDoc.Save(writer);
            }
 
            private void DumpFlowNode(FlowNode node)
            {
                FixedElement element = node.Cookie as FixedElement;
                if (element != null)
                {
                    XmlElement newDumpNode = _dumpDoc.CreateElement(element.Type.Name);
                    newDumpNode.SetAttribute("id", node.ScopeId.ToString());
                    _currentDumpNode.AppendChild(newDumpNode);
                    _currentDumpNode = newDumpNode;
                }
                else
                {
                    //Run
                    int nodeLength = (int) node.Cookie;
                    _currentDumpNode.SetAttribute("Cookie", nodeLength.ToString());
                    FixedSOMElement[] somElems = node.FixedSOMElements;
                    if (somElems!=null)
                    {
                        StringBuilder strBuilder = new StringBuilder();
                        foreach (FixedSOMElement somElem in somElems)
                        {
                            FixedSOMTextRun run = somElem as FixedSOMTextRun;
                            if (run != null)
                            {
                                strBuilder.Append(run.Text);
                            }
                        }
                        _currentDumpNode.InnerText = strBuilder.ToString();
                    }
                }
 
            }
#endif
 
            private void _AddStartNode(FixedElement.ElementType type)
            {
                FlowNode startNode = new FlowNode(_NewScopeId(), FlowNodeType.Start, _pageIndex);
                FlowNode endNode = new FlowNode(_NewScopeId(), FlowNodeType.End, _pageIndex);
                // add fixed element
                _container.OnNewFlowElement(_currentFixedElement, //_container.ContainerElement,
                type,
                new FlowPosition(_container, (FlowNode)startNode, 1),
                new FlowPosition(_container, (FlowNode)endNode, 0),
                null,
                _pageIndex
                );
                // Create fixed element
                _fixedElements.Push(_currentFixedElement);
                _currentFixedElement = (FixedElement)startNode.Cookie;
                _flowNodes.Add(startNode);
                _endNodes.Push(endNode);
#if DEBUG
                DumpFlowNode(startNode);
#endif
 
 
            }
 
            private void _AddEndNode()
            {
                _flowNodes.Add((FlowNode)_endNodes.Pop());
                _currentFixedElement = (FixedElement)_fixedElements.Pop();
#if DEBUG
                _currentDumpNode = (XmlElement) _currentDumpNode.ParentNode;
#endif
            }
 
            private void _FinishTextRun(bool addSpace)
            {
                if (_textRuns.Count > 0)
                {
                    int textRunLength = 0;
                    FixedSOMTextRun run = null;
                    for (int i=0; i<_textRuns.Count; i++)
                    {
                        run = _textRuns[i];
                        Glyphs glyphs = _builder.GetGlyphsElement(run.FixedNode);
                        GlyphComparison comparison = _builder._CompareGlyphs(_lastGlyphs, glyphs);
 
                        if (comparison == GlyphComparison.DifferentLine)
                        {
                            _FinishLine();
                        }
 
                        _lastGlyphs = glyphs;
 
                        _lineLayoutBox.Union(run.BoundingRect);
                        run.LineIndex = _lineResults.Count;
                        if (_nodesInLine.Count == 0 || _nodesInLine[_nodesInLine.Count - 1] != run.FixedNode)
                        {
                            _nodesInLine.Add(run.FixedNode);
                        }
 
                        textRunLength += run.EndIndex - run.StartIndex;
                        Debug.Assert(run.EndIndex - run.StartIndex == run.Text.Length);
 
                        if (i>0 && _builder._IsNonContiguous(_textRuns[i-1], run, comparison))
                        {
                            _textRuns[i-1].Text = $"{_textRuns[i - 1].Text} ";
                            textRunLength++;
                        }
                    }
                    if (addSpace && run.Text.Length>0 && !run.Text.EndsWith(" ", StringComparison.Ordinal) && !IsHyphen(run.Text[run.Text.Length - 1]))
                    {
                        run.Text = $"{run.Text} ";
                        textRunLength ++;
                    }
 
                    if (textRunLength != 0)
                    {
                        FlowNode flowNodeRun = new FlowNode(_NewScopeId(), FlowNodeType.Run, textRunLength);
                        // Add list of text runs to flow node
                        flowNodeRun.FixedSOMElements = _textRuns.ToArray();
 
                        int offset = 0;
 
                        foreach (FixedSOMTextRun textRun in _textRuns)
                        {
                            textRun.FlowNode = flowNodeRun;
                            textRun.OffsetInFlowNode = offset;
                            _mapping.AddFixedElement(textRun);
 
                            offset += textRun.Text.Length;
                        }
 
                        //Debug.Assert(offset == textRunLength);
 
                        _flowNodes.Add(flowNodeRun);
#if DEBUG
                        DumpFlowNode(flowNodeRun);
#endif
 
                        // clear the list
                        _textRuns.Clear();
                    }
                }
 
                //Close the inline tag if any
                if (_currentRun != null)
                {
                    _AddEndNode();
                    _currentRun = null;
                }
            }
 
            private void _FinishHyperlink()
            {
                if (_currentNavUri != null)
                {
                    _AddEndNode(); // </Hyperlink>
                    _currentNavUri = null;
                }
            }
 
            private void _SetHyperlink(Uri navUri, FixedNode node, UIElement shadowHyperlink)
            {
                if (navUri != _currentNavUri || (navUri != null && navUri.ToString() != _currentNavUri.ToString()))
                {
                    if (_currentNavUri != null)
                    {
                        _AddEndNode(); // </Hyperlink>
                    }
 
                    if (navUri != null)
                    {
                        _AddStartNode(FixedElement.ElementType.Hyperlink);
                        _currentFixedElement.SetValue(Hyperlink.NavigateUriProperty, navUri);
                        UIElement uiElement = _fixedPage.GetElement(node) as UIElement;
                        Debug.Assert(uiElement != null);
                        if (uiElement != null)
                        {
                            _currentFixedElement.SetValue(FixedElement.HelpTextProperty, (String) (uiElement.GetValue(AutomationProperties.HelpTextProperty)));
                            _currentFixedElement.SetValue(FixedElement.NameProperty, (String) (uiElement.GetValue(AutomationProperties.NameProperty)));
                            if (shadowHyperlink != null)
                            {
                                _logicalHyperlinkContainer.MarkAsUsed(shadowHyperlink);
                            }
                        }
                    }
 
                    _currentNavUri = navUri;
                }
            }
 
            private void _FinishLine()
            {
                if (_nodesInLine.Count > 0)
                {
                    FixedLineResult newLineResult = new FixedLineResult(_nodesInLine.ToArray(), _lineLayoutBox);
                    Debug.Assert(newLineResult != null);
 
                    _lineResults.Add(newLineResult);
 
                    _nodesInLine.Clear();
                    _lineLayoutBox = Rect.Empty;
                }
            }
 
            private int _NewScopeId()
            {
                return _builder._nextScopeId++;
            }
 
            public FixedElement FixedElement
            {
                get
                {
                    return _currentFixedElement;
                }
            }
 
            private int _pageIndex;
            private FixedTextContainer _container;
            private FixedTextBuilder _builder;
            private List<FixedSOMTextRun> _textRuns;
            private List<FlowNode> _flowNodes;
            private List<FixedNode> _fixedNodes;
            private List<FixedNode> _nodesInLine;
            private List<FixedLineResult> _lineResults;
            private Rect _lineLayoutBox;
            private Stack _endNodes;
            private Stack _fixedElements;
            private FixedElement _currentFixedElement;
            private FixedFlowMap _mapping;
            private FixedPageStructure _pageStructure;
            private Glyphs _lastGlyphs;
            private FixedSOMTextRun _currentRun;
            private LogicalHyperlinkContainer _logicalHyperlinkContainer;
            private FixedPage _fixedPage;
            private Uri _currentNavUri;
#if DEBUG
            private XmlDocument _dumpDoc;
            private XmlElement _currentDumpNode;
#endif
 
        }
 
 
        internal enum GlyphComparison
        {
            DifferentLine,
            SameLine,
            Adjacent, //also on same line
            Unknown
        }
 
        #endregion
    }
}