File: MS\Internal\PtsHost\FloaterParaClient.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: FloaterParaClient class is responsible for handling display
//              related data of paragraphs associated with floaters.
//
 
 
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Documents;
using MS.Internal.Documents;
using MS.Internal.Text;
 
using MS.Internal.PtsHost.UnsafeNativeMethods;
 
namespace MS.Internal.PtsHost
{
    // ----------------------------------------------------------------------
    // FloaterParaClient class is responsible for handling display related
    // data of paragraphs associated with nested floaters.
    // ----------------------------------------------------------------------
    internal sealed class FloaterParaClient : FloaterBaseParaClient
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------
 
        #region Constructors
 
        // ------------------------------------------------------------------
        // Constructor.
        //
        //      paragraph - Paragraph associated with this object.
        // ------------------------------------------------------------------
        internal FloaterParaClient(FloaterParagraph paragraph)
            : base(paragraph)
        {
        }
 
        // ------------------------------------------------------------------
        // IDisposable.Dispose
        // ------------------------------------------------------------------
        public override void Dispose()
        {
            if(_pageContext != null)
            {
                _pageContext.RemoveFloatingParaClient(this);
            }
 
            base.Dispose();
        }
 
        #endregion Constructors
 
        protected override void OnArrange()
        {
            base.OnArrange();
 
            ((FloaterParagraph)Paragraph).UpdateSegmentLastFormatPositions();
 
            // Query subpage details
            PTS.FSSUBPAGEDETAILS subpageDetails;
            PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
            _pageContext.AddFloatingParaClient(this);
 
            // Arrange subpage content. Subpage content may be simple or complex -
            // depending of set of features used in the content of the subpage.
            // (1) simple subpage (contains only one track)
            // (2) complex subpage (contains columns)
            if (PTS.ToBoolean(subpageDetails.fSimple))
            {
                _pageContextOfThisPage.PageRect = new PTS.FSRECT(subpageDetails.u.simple.trackdescr.fsrc);
 
                // (1) simple subpage (contains only one track)
                // Exceptions don't need to pop, as the top level arrange context will be nulled out if thrown.
                Paragraph.StructuralCache.CurrentArrangeContext.PushNewPageData(_pageContextOfThisPage, subpageDetails.u.simple.trackdescr.fsrc, 
                                                                                Paragraph.StructuralCache.CurrentArrangeContext.FinitePage);
 
                PtsHelper.ArrangeTrack(PtsContext, ref subpageDetails.u.simple.trackdescr, subpageDetails.u.simple.fswdir);
 
                Paragraph.StructuralCache.CurrentArrangeContext.PopPageData();
            }
            else
            {
                _pageContextOfThisPage.PageRect = new PTS.FSRECT(subpageDetails.u.complex.fsrc);
 
                // (2) complex page (contains columns)
                // cBasicColumns == 0, means that subpage content is empty
                if (subpageDetails.u.complex.cBasicColumns != 0)
                {
                    // Retrieve description for each column.
                    PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                    PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                    // Arrange each track
                    for (int index = 0; index < arrayColumnDesc.Length; index++)
                    {
                        // Exceptions don't need to pop, as the top level arrange context will be nulled out if thrown.
                        Paragraph.StructuralCache.CurrentArrangeContext.PushNewPageData(_pageContextOfThisPage, arrayColumnDesc[index].fsrc,
                                                                                        Paragraph.StructuralCache.CurrentArrangeContext.FinitePage);
 
                        PtsHelper.ArrangeTrack(PtsContext, ref arrayColumnDesc[index], subpageDetails.u.complex.fswdir);
 
                        Paragraph.StructuralCache.CurrentArrangeContext.PopPageData();
                    }
                }
            }
        }
 
        // ------------------------------------------------------------------
        // Updates viewport
        // ------------------------------------------------------------------
        internal override void UpdateViewport(ref PTS.FSRECT viewport)
        {
            // Query subpage details
            PTS.FSSUBPAGEDETAILS subpageDetails;
            PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
            PTS.FSRECT viewportSubpage = new PTS.FSRECT();
 
            viewportSubpage.u = viewport.u - ContentRect.u;
            viewportSubpage.v = viewport.v - ContentRect.v;
            viewportSubpage.du = viewport.du;
            viewportSubpage.dv = viewport.dv;
 
            // Subpage content may be simple or complex -
            // depending of set of features used in the content of the subpage.
            // (1) simple subpage (contains only one track)
            // (2) complex subpage (contains columns)
            if (PTS.ToBoolean(subpageDetails.fSimple))
            {
                PtsHelper.UpdateViewportTrack(PtsContext, ref subpageDetails.u.simple.trackdescr, ref viewportSubpage);
            }
            else
            {
                // (2) complex page (contains columns)
                // cBasicColumns == 0, means that subpage content is empty
                bool emptySubpage = (subpageDetails.u.complex.cBasicColumns == 0);
                if (!emptySubpage)
                {
                    // Retrieve description for each column.
                    PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                    PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                    emptySubpage = (arrayColumnDesc.Length == 0);
                    if (!emptySubpage)
                    {
                        for (int index = 0; index < arrayColumnDesc.Length; index++)
                        {
                            PtsHelper.UpdateViewportTrack(PtsContext, ref arrayColumnDesc[index], ref viewportSubpage);
                        }
                    }
                }
            }
        }
 
 
        // ------------------------------------------------------------------
        // Arrange floater
        //
        //      rcFloater - rectangle of the floater
        //      rcHostPara - rectangle of the host text paragraph.
        // ------------------------------------------------------------------
        internal override void ArrangeFloater(PTS.FSRECT rcFloater, PTS.FSRECT rcHostPara, uint fswdirParent, PageContext pageContext)
        {
            // If alignment is set to Stretch and floater width is less than para width, center it
            if (IsFloaterHorizontallyCentered(rcFloater, rcHostPara))
            {
                rcFloater.u = rcHostPara.u + (rcHostPara.du - rcFloater.du) / 2;
            }
 
            // Set paragraph rectangle (relative to the page)
            _rect = rcFloater;
 
            // Adjust rect to account for margins
            // Add margin values to rect offsets and subtract them from rect widths
            MbpInfo mbp = MbpInfo.FromElement(Paragraph.Element, Paragraph.StructuralCache.TextFormatterHost.PixelsPerDip);
            _rect.v += mbp.MarginTop;
            _rect.dv -= mbp.MarginTop + mbp.MarginBottom;
            _rect.u += mbp.MarginLeft;
            _rect.du -= mbp.MarginLeft + mbp.MarginRight;
 
            _flowDirectionParent = PTS.FswdirToFlowDirection(fswdirParent);
            _flowDirection = (FlowDirection)Paragraph.Element.GetValue(FrameworkElement.FlowDirectionProperty);
            _pageContext = pageContext;
 
            if(ThisFlowDirection != PageFlowDirection)
            {
                mbp.MirrorBP();
            }
 
            _contentRect.u = _rect.u + mbp.BPLeft;
            _contentRect.du = Math.Max(TextDpi.ToTextDpi(TextDpi.MinWidth), _rect.du - mbp.BPRight - mbp.BPLeft);
            _contentRect.v = _rect.v + mbp.BPTop;
            _contentRect.dv = Math.Max(TextDpi.ToTextDpi(TextDpi.MinWidth), _rect.dv - mbp.BPBottom - mbp.BPTop);
 
            _paddingRect.u = _rect.u + mbp.BorderLeft;
            _paddingRect.du = Math.Max(TextDpi.ToTextDpi(TextDpi.MinWidth), _rect.du - mbp.BorderRight - mbp.BorderLeft);
            _paddingRect.v = _rect.v + mbp.BorderTop;
            _paddingRect.dv = Math.Max(TextDpi.ToTextDpi(TextDpi.MinWidth), _rect.dv - mbp.BorderBottom - mbp.BorderTop);
 
            // Do paragraph specifc arrange
            OnArrange();
        }
 
        // ------------------------------------------------------------------
        // Hit tests to the correct IInputElement within the paragraph
        // that the mouse is over.
        // ------------------------------------------------------------------
        internal override IInputElement InputHitTest(PTS.FSPOINT pt)
        {
            IInputElement ie = null;
 
 
            if(_pageContextOfThisPage.FloatingElementList != null)
            {
                for(int index = 0; index < _pageContextOfThisPage.FloatingElementList.Count && ie == null; index++)
                {
                    BaseParaClient floatingElement = _pageContextOfThisPage.FloatingElementList[index];
 
                    ie = floatingElement.InputHitTest(pt);
                }
            }
 
            if(ie == null)
            {
                // Query subpage details
                PTS.FSSUBPAGEDETAILS subpageDetails;
                PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
                if(Rect.Contains(pt))
                {
                    if(ContentRect.Contains(pt))
                    {
                        pt = new PTS.FSPOINT(pt.u - ContentRect.u, pt.v - ContentRect.v);
 
                        // Hittest subpage content. Subpage content may be simple or complex -
                        // depending of set of features used in the content of the page.
                        // (1) simple subpage (contains only one track)
                        // (2) complex subpage (contains columns)
                        if (PTS.ToBoolean(subpageDetails.fSimple))
                        {
                            ie = PtsHelper.InputHitTestTrack(PtsContext, pt, ref subpageDetails.u.simple.trackdescr);
                        }
                        else
                        {
                            // (2) complex page (contains columns)
                            // cBasicColumns == 0, means that subpage content is empty
                            if (subpageDetails.u.complex.cBasicColumns != 0)
                            {
                                // Retrieve description for each column.
                                PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                                PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                                // Arrange each track
                                for (int index = 0; index < arrayColumnDesc.Length && ie == null; index++)
                                {
                                    ie = PtsHelper.InputHitTestTrack(PtsContext, pt, ref arrayColumnDesc[index]);
                                }
                            }
                        }
                    }
 
                    if(ie == null)
                    {
                        ie = Paragraph.Element as IInputElement;
                    }
                }
            }
 
            return ie;
        }
 
        // ------------------------------------------------------------------
        // Returns ArrayList of rectangles for the given ContentElement
        // if it is found. Returns null otherwise.
        // start: int representing start offset of e.
        // length: int representing number of positions occupied by e.
        // ------------------------------------------------------------------
        internal override List<Rect> GetRectangles(ContentElement e, int start, int length)
        {
            List<Rect> rectangles = new List<Rect>();
 
            if (Paragraph.Element as ContentElement == e)
            {
                // We have found the element. Return rectangles for this paragraph.
                GetRectanglesForParagraphElement(out rectangles);
            }
            else
            {
                // Query subpage details
                PTS.FSSUBPAGEDETAILS subpageDetails;
                PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
                // Check subpage content for element. Subpage content may be simple or complex -
                // depending of set of features used in the content of the page.
                // (1) simple subpage (contains only one track)
                // (2) complex subpage (contains columns)
                if (PTS.ToBoolean(subpageDetails.fSimple))
                {
                    // (1) simple subpage (contains only one track)
                    rectangles = PtsHelper.GetRectanglesInTrack(PtsContext, e, start, length, ref subpageDetails.u.simple.trackdescr);
                }
                else
                {
                    // (2) complex page (contains columns)
                    // cBasicColumns == 0, means that subpage content is empty
                    if (subpageDetails.u.complex.cBasicColumns != 0)
                    {
                        // Retrieve description for each column.
                        PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                        PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                        // Arrange each track
                        for (int index = 0; index < arrayColumnDesc.Length; index++)
                        {
                            List<Rect> trackRectangles = PtsHelper.GetRectanglesInTrack(PtsContext, e, start, length, ref arrayColumnDesc[index]);
                            Invariant.Assert(trackRectangles != null);
                            if (trackRectangles.Count != 0)
                            {
                                // Add rectangles found in this track to all rectangles
                                rectangles.AddRange(trackRectangles);
                            }
                        }
                    }
                }
 
                rectangles = PtsHelper.OffsetRectangleList(rectangles, TextDpi.FromTextDpi(ContentRect.u), TextDpi.FromTextDpi(ContentRect.v));
            }
 
            Invariant.Assert(rectangles != null);
            return rectangles;
        }
 
 
        // ------------------------------------------------------------------
        // Validate visual node associated with paragraph.
        //
        //      fskupdInherited - inherited update info
        //      fswdir - inherited flow direction
        // ------------------------------------------------------------------
        internal override void ValidateVisual(PTS.FSKUPDATE fskupdInherited)
        {
            // Floater is always reported as NEW. Override PTS inherited value.
            fskupdInherited = PTS.FSKUPDATE.fskupdNew;
 
            // Query subpage details
            PTS.FSSUBPAGEDETAILS subpageDetails;
            PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
            // Obtain all mbd info
            MbpInfo mbp = MbpInfo.FromElement(Paragraph.Element, Paragraph.StructuralCache.TextFormatterHost.PixelsPerDip);
 
            if(ThisFlowDirection != PageFlowDirection)
            {
                mbp.MirrorBP();
            }
 
            Brush backgroundBrush = (Brush)Paragraph.Element.GetValue(TextElement.BackgroundProperty);
            Visual.DrawBackgroundAndBorder(backgroundBrush, mbp.BorderBrush, mbp.Border, _rect.FromTextDpi(), IsFirstChunk, IsLastChunk);
 
 
            ContainerVisual pageContentVisual;
            ContainerVisual floatingElementsVisual;
 
            if(_visual.Children.Count != 2)
            {
                _visual.Children.Clear();
                _visual.Children.Add(new ContainerVisual());
                _visual.Children.Add(new ContainerVisual());
            }
 
            pageContentVisual = (ContainerVisual)_visual.Children[0];
            floatingElementsVisual = (ContainerVisual)_visual.Children[1];
 
            // Subpage content may be simple or complex -
            // depending of set of features used in the content of the subpage.
            // (1) simple subpage (contains only one track)
            // (2) complex subpage (contains columns)
            if (PTS.ToBoolean(subpageDetails.fSimple))
            {
                // (1) simple subpage (contains only one track)
                PTS.FSKUPDATE fskupd = subpageDetails.u.simple.trackdescr.fsupdinf.fskupd;
                if (fskupd == PTS.FSKUPDATE.fskupdInherited)
                {
                    fskupd = fskupdInherited;
                }
                VisualCollection visualChildren = pageContentVisual.Children;
                if (fskupd == PTS.FSKUPDATE.fskupdNew)
                {
                    visualChildren.Clear();
                    visualChildren.Add(new ContainerVisual());
                }
                // For complex subpage SectionVisual is added. So, when morphing
                // complex subpage to simple one, remove SectionVisual.
                else if (visualChildren.Count == 1 && !(visualChildren[0] is ContainerVisual))
                {
                    visualChildren.Clear();
                    visualChildren.Add(new ContainerVisual());
                }
                Debug.Assert(visualChildren.Count == 1 && visualChildren[0] is ContainerVisual);
                ContainerVisual trackVisual = (ContainerVisual)visualChildren[0];
 
                PtsHelper.UpdateTrackVisuals(PtsContext, trackVisual.Children, fskupdInherited, ref subpageDetails.u.simple.trackdescr);
            }
            else
            {
                // (2) complex page (contains columns)
                // cBasicColumns == 0, means that subpage content is empty
                bool emptySubpage = (subpageDetails.u.complex.cBasicColumns == 0);
                if (!emptySubpage)
                {
                    // Retrieve description for each column.
                    PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                    PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                    emptySubpage = (arrayColumnDesc.Length == 0);
                    if (!emptySubpage)
                    {
                        PTS.FSKUPDATE fskupd = fskupdInherited;
                        ErrorHandler.Assert(fskupd != PTS.FSKUPDATE.fskupdShifted, ErrorHandler.UpdateShiftedNotValid);
                        Debug.Assert(fskupd != PTS.FSKUPDATE.fskupdNoChange);
 
                        // For complex subpage SectionVisual is added. So, when morphing
                        // simple subpage to complex one, remove ParagraphVisual.
                        VisualCollection visualChildren = pageContentVisual.Children;
                        if (visualChildren.Count == 0)
                        {
                            visualChildren.Add(new SectionVisual());
                        }
                        else if (!(visualChildren[0] is SectionVisual))
                        {
                            visualChildren.Clear();
                            visualChildren.Add(new SectionVisual());
                        }
                        Debug.Assert(visualChildren.Count == 1 && visualChildren[0] is SectionVisual);
                        SectionVisual sectionVisual = (SectionVisual)visualChildren[0];
 
                        // Draw column rules.
                        ColumnPropertiesGroup columnProperties = new ColumnPropertiesGroup(Paragraph.Element);
                        sectionVisual.DrawColumnRules(ref arrayColumnDesc, TextDpi.FromTextDpi(subpageDetails.u.complex.fsrc.v), TextDpi.FromTextDpi(subpageDetails.u.complex.fsrc.dv), columnProperties);
 
                        visualChildren = sectionVisual.Children;
                        if (fskupd == PTS.FSKUPDATE.fskupdNew)
                        {
                            visualChildren.Clear();
                            for (int index = 0; index < arrayColumnDesc.Length; index++)
                            {
                                visualChildren.Add(new ContainerVisual());
                            }
                        }
                        ErrorHandler.Assert(visualChildren.Count == arrayColumnDesc.Length, ErrorHandler.ColumnVisualCountMismatch);
 
                        for (int index = 0; index < arrayColumnDesc.Length; index++)
                        {
                            ContainerVisual trackVisual = (ContainerVisual)visualChildren[index];
 
                            PtsHelper.UpdateTrackVisuals(PtsContext, trackVisual.Children, fskupdInherited, ref arrayColumnDesc[index]);
                        }
                    }
                }
                if (emptySubpage)
                {
                    // There is no content, remove all existing visuals.
                    _visual.Children.Clear();
                }
            }
 
            pageContentVisual.Offset = new PTS.FSVECTOR(ContentRect.u, ContentRect.v).FromTextDpi();
            floatingElementsVisual.Offset = new PTS.FSVECTOR(ContentRect.u, ContentRect.v).FromTextDpi();
 
            PTS.FSRECT clipRect = new PTS.FSRECT(_paddingRect.u - _contentRect.u, _paddingRect.v - _contentRect.v, _paddingRect.du, _paddingRect.dv);
            PtsHelper.ClipChildrenToRect(_visual, clipRect.FromTextDpi());
 
            PtsHelper.UpdateFloatingElementVisuals(floatingElementsVisual, _pageContextOfThisPage.FloatingElementList);
        }
 
        // ------------------------------------------------------------------
        // Create paragraph result representing this paragraph.
        // ------------------------------------------------------------------
        internal override ParagraphResult CreateParagraphResult()
        {
            return new FloaterParagraphResult(this);
        }
 
        // ------------------------------------------------------------------
        // Return TextContentRange for the content of the paragraph.
        // ------------------------------------------------------------------
        internal override TextContentRange GetTextContentRange()
        {
            TextContentRange textContentRange;
 
            // Query subpage details
            PTS.FSSUBPAGEDETAILS subpageDetails;
            PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
            // Subpage content may be simple or complex -
            // depending of set of features used in the content of the page.
            // (1) simple subpage (contains only one track)
            // (2) complex subpage (contains columns)
            if (PTS.ToBoolean(subpageDetails.fSimple))
            {
                // (1) simple subpage (contains only one track)
                textContentRange = PtsHelper.TextContentRangeFromTrack(PtsContext, subpageDetails.u.simple.trackdescr.pfstrack);
            }
            else
            {
                textContentRange = new TextContentRange();
 
                // (2) complex page (contains columns)
                // cBasicColumns == 0, means that subpage content is empty
                if (subpageDetails.u.complex.cBasicColumns != 0)
                {
                    // Retrieve description for each column.
                    PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                    PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                    // Arrange each track
                    for (int index = 0; index < arrayColumnDesc.Length; index++)
                    {
                        // Merge TextContentRanges for all columns
                        textContentRange.Merge(PtsHelper.TextContentRangeFromTrack(PtsContext, arrayColumnDesc[index].pfstrack));
                    }
                }
            }
 
            // Beginning and end tags should be consumed by host para. 
 
            // If the first paragraph is the first paragraph in the paraclient and it is the first chunk, 
            // include start position of this element.
            if (IsFirstChunk)
            {
                textContentRange.Merge(TextContainerHelper.GetTextContentRangeForTextElementEdge(
                    Paragraph.Element as TextElement, ElementEdge.BeforeStart));
            }
 
            // If the last paragraph is the last paragraph in the paraclient and it is the last chunk, 
            // include end position of this element.
            if (IsLastChunk)
            {
                textContentRange.Merge(TextContainerHelper.GetTextContentRangeForTextElementEdge(
                    Paragraph.Element as TextElement, ElementEdge.AfterEnd));
            }
 
            return textContentRange;
        }
 
        // ------------------------------------------------------------------
        // Returns a new colleciton of ParagraphResults for the contained paragraphs.
        // ------------------------------------------------------------------
        private ReadOnlyCollection<ParagraphResult> GetChildrenParagraphResults(out bool hasTextContent)
        {
            List<ParagraphResult> paragraphResults;
 
            // Query subpage details
            PTS.FSSUBPAGEDETAILS subpageDetails;
            PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
            // hasTextContent is set to true if any of the children paragraphs has text content, not just attached objects
            hasTextContent = false;
 
            // Subpage content may be simple or complex -
            // depending of set of features used in the content of the page.
            // (1) simple subpage (contains only one track)
            // (2) complex subpage (contains columns)
            if (PTS.ToBoolean(subpageDetails.fSimple))
            {
                // (1) simple subpage (contains only one track)
                // Get track details
                PTS.FSTRACKDETAILS trackDetails;
                PTS.Validate(PTS.FsQueryTrackDetails(PtsContext.Context, subpageDetails.u.simple.trackdescr.pfstrack, out trackDetails));
                hasTextContent = true;
 
                if (trackDetails.cParas == 0) 
                {
                    return new ReadOnlyCollection<ParagraphResult>(new List<ParagraphResult>(0));  
                }
 
                // Get list of paragraphs
                PTS.FSPARADESCRIPTION[] arrayParaDesc;
                PtsHelper.ParaListFromTrack(PtsContext, subpageDetails.u.simple.trackdescr.pfstrack, ref trackDetails, out arrayParaDesc);
 
                paragraphResults = new List<ParagraphResult>(arrayParaDesc.Length);
                for (int i = 0; i < arrayParaDesc.Length; i++)
                {
                    BaseParaClient paraClient = PtsContext.HandleToObject(arrayParaDesc[i].pfsparaclient) as BaseParaClient;
                    PTS.ValidateHandle(paraClient);
                    ParagraphResult paragraphResult = paraClient.CreateParagraphResult();
                    if (paragraphResult.HasTextContent)
                    {
                        hasTextContent = true;
                    }
                    paragraphResults.Add(paragraphResult);
                }
            }
            else
            {
                // (2) complex page (contains columns)
                // cBasicColumns == 0, means that subpage content is empty
                if (subpageDetails.u.complex.cBasicColumns == 0) 
                {
                    return new ReadOnlyCollection<ParagraphResult>(new List<ParagraphResult>(0));
                }
 
                // Retrieve description for each column.
                PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
                Debug.Assert(arrayColumnDesc.Length == 1);
 
                // Get track details
                PTS.FSTRACKDETAILS trackDetails;
                PTS.Validate(PTS.FsQueryTrackDetails(PtsContext.Context, arrayColumnDesc[0].pfstrack, out trackDetails));
 
                if (trackDetails.cParas == 0) 
                {
                    return new ReadOnlyCollection<ParagraphResult>(new List<ParagraphResult>(0));
                }
 
                // Get list of paragraphs
                PTS.FSPARADESCRIPTION[] arrayParaDesc;
                PtsHelper.ParaListFromTrack(PtsContext, arrayColumnDesc[0].pfstrack, ref trackDetails, out arrayParaDesc);
 
                paragraphResults = new List<ParagraphResult>(arrayParaDesc.Length);
                for (int i = 0; i < arrayParaDesc.Length; i++)
                {
                    BaseParaClient paraClient = PtsContext.HandleToObject(arrayParaDesc[i].pfsparaclient) as BaseParaClient;
                    PTS.ValidateHandle(paraClient);
                    ParagraphResult paragraphResult = paraClient.CreateParagraphResult();
                    if (paragraphResult.HasTextContent)
                    {
                        hasTextContent = true;
                    }
                    paragraphResults.Add(paragraphResult);
                }
            }
            return new ReadOnlyCollection<ParagraphResult>(paragraphResults);
        }
 
        /// <summary>
        /// Returns a new collection of ColumnResults for the subpage. Will always 
        /// have at least one column.
        /// </summary>
        /// <param name="hasTextContent">
        /// True if any column in the floater has text content, i.e. does not contain only figures/floaters
        /// </param>
        internal ReadOnlyCollection<ColumnResult> GetColumnResults(out bool hasTextContent)
        {
            List<ColumnResult> columnResults = new List<ColumnResult>(0);
            Vector contentOffset = new Vector();
 
            // hasTextContent is set to true if any of the columns has text content, not just attached objects. A column has text content if any of
            // it's paragraph results has text content
            hasTextContent = false;
 
            // Query subpage details
            PTS.FSSUBPAGEDETAILS subpageDetails;
            PTS.Validate(PTS.FsQuerySubpageDetails(PtsContext.Context, _paraHandle, out subpageDetails));
 
            // Subpage content may be simple or complex -
            // depending of set of features used in the content of the page.
            // (1) simple subpage (contains only one track)
            // (2) complex subpage (contains columns)
            if (PTS.ToBoolean(subpageDetails.fSimple))
            {
                // (1) simple subpage (contains only one track)
                PTS.FSTRACKDETAILS trackDetails;
                PTS.Validate(PTS.FsQueryTrackDetails(PtsContext.Context, subpageDetails.u.simple.trackdescr.pfstrack, out trackDetails));
                if (trackDetails.cParas > 0)
                {
                    columnResults = new List<ColumnResult>(1);
                    ColumnResult columnResult = new ColumnResult(this, ref subpageDetails.u.simple.trackdescr, contentOffset);
                    columnResults.Add(columnResult);
                    if (columnResult.HasTextContent)
                    {
                        hasTextContent = true;
                    }
                }
            }
            else
            {
                // (2) complex page (contains columns)
                // cBasicColumns == 0, means that subpage content is empty
                if (subpageDetails.u.complex.cBasicColumns != 0)
                {
                    // Retrieve description for each column.
                    PTS.FSTRACKDESCRIPTION[] arrayColumnDesc;
                    PtsHelper.TrackListFromSubpage(PtsContext, _paraHandle, ref subpageDetails, out arrayColumnDesc);
 
                    // Floaters are held at one column; just add the first one
                    columnResults = new List<ColumnResult>(1);
                    PTS.FSTRACKDETAILS trackDetails;
                    PTS.Validate(PTS.FsQueryTrackDetails(PtsContext.Context, arrayColumnDesc[0].pfstrack, out trackDetails));
                    if (trackDetails.cParas > 0)
                    {                            
                        ColumnResult columnResult = new ColumnResult(this, ref arrayColumnDesc[0], contentOffset);
                        columnResults.Add(columnResult);
                        if (columnResult.HasTextContent)
                        {
                            hasTextContent = true;
                        }
                    }
                }
            }
 
            return new ReadOnlyCollection<ColumnResult>(columnResults);
        }
 
        // ------------------------------------------------------------------
        // Returns a collection of ParagraphResults for the column's paragraphs.
        //
        //      pfstrack - Pointer to PTS track representing a column.
        //      parentOffset - Parent offset from the top of the page.
        //      hasTextContent - true if any paragraph has text content
        // ------------------------------------------------------------------
        internal ReadOnlyCollection<ParagraphResult> GetParagraphResultsFromColumn(IntPtr pfstrack, Vector parentOffset, out bool hasTextContent)
        {
            // Floater has only one column. This is the same as getting children paragraphs.
            return GetChildrenParagraphResults(out hasTextContent);
        }
 
        // ------------------------------------------------------------------
        // Retrieves text range for contents of the column represented by
        // 'pfstrack'.
        //
        //      pfstrack - Pointer to PTS track representing a column.
        // ------------------------------------------------------------------
        internal TextContentRange GetTextContentRangeFromColumn(IntPtr pfstrack)
        {
            // Floater has only one column.
            return GetTextContentRange();
        }
 
        /// <summary>
        /// Returns tight bounding path geometry.
        /// </summary>
        internal Geometry GetTightBoundingGeometryFromTextPositions(ReadOnlyCollection<ColumnResult> columns, ReadOnlyCollection<ParagraphResult> floatingElements, ITextPointer startPosition, ITextPointer endPosition, Rect visibleRect)
        {
            Geometry geometry = null;
 
            // Floater always has one column, so we can skip getting a column from the text position range
            Invariant.Assert(columns != null && columns.Count <= 1, "Columns collection is null.");
            Invariant.Assert(floatingElements != null, "Floating element collection is null.");
            ReadOnlyCollection<ParagraphResult> paragraphs = (columns.Count > 0) ? columns[0].Paragraphs : new ReadOnlyCollection<ParagraphResult>(new List<ParagraphResult>(0));
 
            if (paragraphs.Count > 0 || floatingElements.Count > 0)
            {
                geometry = TextDocumentView.GetTightBoundingGeometryFromTextPositionsHelper(paragraphs, floatingElements, startPosition, endPosition, TextDpi.FromTextDpi(_dvrTopSpace), visibleRect);
 
                //  restrict geometry to the floater's content rect boundary.
                //  because of end-of-line / end-of-para simulation calculated geometry could be larger. 
                Rect viewport = new Rect(0, 0, TextDpi.FromTextDpi(_contentRect.du), TextDpi.FromTextDpi(_contentRect.dv));
                CaretElement.ClipGeometryByViewport(ref geometry, viewport);
            }
            return (geometry);
        }
 
        // ------------------------------------------------------------------
        // Handle to PTS subpage object.
        // ------------------------------------------------------------------
        internal IntPtr SubpageHandle
        {
            // getter removed (unused internal API).  If needed, recall from history.
 
            set { _paraHandle = value; }
        }
 
        // ------------------------------------------------------------------
        // Rect of content in page coordinate system
        // ------------------------------------------------------------------
        internal PTS.FSRECT ContentRect { get { return _contentRect; } }
 
        // Floating element list
        internal ReadOnlyCollection<ParagraphResult> FloatingElementResults
        {
            get
            {
                List<ParagraphResult> floatingElements = new List<ParagraphResult>(0);
                List<BaseParaClient> floatingElementList = _pageContextOfThisPage.FloatingElementList;
                if (floatingElementList != null)
                {
                    for (int i = 0; i < floatingElementList.Count; i++)
                    {
                        ParagraphResult paragraphResult = floatingElementList[i].CreateParagraphResult();
                        floatingElements.Add(paragraphResult);
                    }
                }
                return new ReadOnlyCollection<ParagraphResult>(floatingElements);
            }
        }
 
 
        // ------------------------------------------------------------------
        // Returns whether a floater should be horizontally centered
        // ------------------------------------------------------------------
        private bool IsFloaterHorizontallyCentered(PTS.FSRECT rcFloater, PTS.FSRECT rcHostPara)
        {
            Floater floater = Paragraph.Element as Floater;
 
            if(floater == null)
            {
                return false;
            }
 
            if(floater.HorizontalAlignment != HorizontalAlignment.Stretch)
            {
                return false;
            }
 
            if(rcFloater.du >= rcHostPara.du)
            {
                return false;
            }
 
            return true;
}
 
        private PTS.FSRECT _contentRect;
        private PTS.FSRECT _paddingRect;
 
        // Page context this para client provides.
        private PageContext _pageContextOfThisPage = new PageContext(); 
    }
}