/// <summary>
 /// Add non-zero geometry bounds to the bounds list
 /// </summary>
 private void AddValidTextBounds(
     ArrayList      boundsList,
     TextBounds     bounds
     )
 {
     if (bounds.Rectangle.Width != 0 && bounds.Rectangle.Height != 0)
     {
         boundsList.Add(bounds);
     }
 }
            /// <summary>
            /// Client to get an array of bounding rectangles of a range of characters within a text line.
            /// </summary>
            /// <param name="firstTextSourceCharacterIndex">index of first character of specified range</param>
            /// <param name="textLength">number of characters of the specified range</param>
            /// <returns>an array of bounding rectangles.</returns>
            public override IList<TextBounds> GetTextBounds(
                int     firstTextSourceCharacterIndex,
                int     textLength
                )
            {
                if ((_statusFlags & StatusFlags.IsDisposed) != 0)
                {
                    throw new ObjectDisposedException(SR.Get(SRID.TextLineHasBeenDisposed));
                }

                if(textLength == 0)
                {
                    throw new ArgumentOutOfRangeException("textLength", SR.Get(SRID.ParameterMustBeGreaterThanZero));
                }

                if(textLength < 0)
                {
                    firstTextSourceCharacterIndex += textLength;
                    textLength = -textLength;
                }

                if(firstTextSourceCharacterIndex < _cpFirst)
                {
                    textLength += (firstTextSourceCharacterIndex - _cpFirst);
                    firstTextSourceCharacterIndex = _cpFirst;
                }

                if(firstTextSourceCharacterIndex > _cpFirst + _metrics._cchLength - textLength)
                {
                    textLength = (_cpFirst + _metrics._cchLength - firstTextSourceCharacterIndex);
                }

                if (_ploline.Value == IntPtr.Zero)
                {
                    return CreateDegenerateBounds();
                }

                Point position = new Point(0,0);


                // get first cp sublines & text cell

                int firstDepth;
                LsTextCell firstTextCell;
                LsQSubInfo[] firstSublines = new LsQSubInfo[_depthQueryMax];

                int lscpFirst = GetInternalCp(firstTextSourceCharacterIndex);

                QueryLineCpPpoint(
                    lscpFirst,
                    firstSublines,
                    out firstDepth,
                    out firstTextCell
                    );

                if(firstDepth <= 0)
                {
                    // this happens for empty line (line containing only EOP)
                    return CreateDegenerateBounds();
                }


                // get last cp sublines & text cell

                int lastDepth;
                LsTextCell lastTextCell;
                LsQSubInfo[] lastSublines = new LsQSubInfo[_depthQueryMax];

                int lscpEnd = GetInternalCp(firstTextSourceCharacterIndex + textLength - 1);

                QueryLineCpPpoint(
                    lscpEnd,
                    lastSublines,
                    out lastDepth,
                    out lastTextCell
                    );

                if(lastDepth <= 0)
                {
                    // This should never happen but if it does, we still cant throw here.
                    // We must return something even though it's a degenerate bounds or
                    // client hittesting code will just crash.
                    Debug.Assert(false);
                    return CreateDegenerateBounds();
                }

                // check if collapsing symbol is wholely selected
                bool collapsingSymbolSelected =
                    (   _collapsingSymbol != null
                    &&  _collapsedRange != null
                    &&  firstTextSourceCharacterIndex < _collapsedRange.TextSourceCharacterIndex
                    &&  firstTextSourceCharacterIndex + textLength - _collapsedRange.TextSourceCharacterIndex > _collapsedRange.Length / 2
                    );

                TextBounds[] bounds = null;
                ArrayList boundsList = null;

                // By default, if the hittested CP is visible, then we want cpFirst to hit
                // on the leading edge of the first visible cp, and cpEnd to hit on the trailing edge of the
                // last visible cp.
                bool isCpFirstTrailing = false;
                bool isCpEndTrailing = true;

                if (lscpFirst > firstTextCell.lscpEndCell)
                {
                   // when cpFirst is after the last visible cp, then it hits the trailing edge of that cp
                   isCpFirstTrailing = true;
                }

                if (lscpEnd < lastTextCell.lscpStartCell)
                {
                   // when cpEnd is before the first visible cp, then it hits the leading edge of that cp
                   isCpEndTrailing = false;
                }

                if (firstDepth == lastDepth && firstSublines[firstDepth - 1].lscpFirstSubLine == lastSublines[lastDepth - 1].lscpFirstSubLine)
                {
                    // first and last cp are within the same subline

                    int count = collapsingSymbolSelected ? 2 : 1;

                    bounds = new TextBounds[count];
                    bounds[0] =
                        new TextBounds(
                            LSRun.RectUV(
                                position,
                                new LSPOINT(
                                    LSLineUToParagraphU(
                                        GetDistanceInsideTextCell(
                                            lscpFirst,
                                            isCpFirstTrailing,
                                            firstSublines,
                                            firstDepth,
                                            ref firstTextCell
                                            ) + firstTextCell.pointUvStartCell.x
                                        ),
                                    0
                                    ),
                                new LSPOINT(
                                    LSLineUToParagraphU(
                                        GetDistanceInsideTextCell(
                                            lscpEnd,
                                            isCpEndTrailing,
                                            lastSublines,
                                            lastDepth,
                                            ref lastTextCell
                                            ) + lastTextCell.pointUvStartCell.x
                                        ),
                                    _metrics._height
                                    ),
                                this
                                ),
                            Convert.LsTFlowToFlowDirection(firstSublines[firstDepth - 1].lstflowSubLine),
                            CalculateTextRunBounds(lscpFirst, lscpEnd + 1)
                            );

                    if (count > 1)
                    {
                        bounds[1] = CreateCollapsingSymbolBounds();
                    }
                }
                else
                {
                    // first and last cp are not in the same subline.
                    boundsList = new ArrayList(2);

                    int lscpCurrent = lscpFirst;

                    // The hittested cp can be outside of the returned sublines when it is a hidden cp.
                    // We should not pass beyond the end of the returned sublines.
                    int lscpEndInSubline = Math.Min(
                        lscpEnd,
                        lastSublines[lastDepth - 1].lscpFirstSubLine + lastSublines[lastDepth - 1].lsdcpSubLine - 1
                        );

                    int currentDistance = GetDistanceInsideTextCell(
                        lscpFirst,
                        isCpFirstTrailing,
                        firstSublines,
                        firstDepth,
                        ref firstTextCell
                    ) + firstTextCell.pointUvStartCell.x;

                    int baseLevelDepth;

                    CollectTextBoundsToBaseLevel(
                        boundsList,
                        ref lscpCurrent,
                        ref currentDistance,
                        firstSublines,
                        firstDepth,
                        lscpEndInSubline,
                        out baseLevelDepth
                    );

                    if (baseLevelDepth < lastDepth)
                    {
                        CollectTextBoundsFromBaseLevel(
                            boundsList,
                            ref lscpCurrent,
                            ref currentDistance,
                            lastSublines,
                            lastDepth,
                            baseLevelDepth
                        );
                    }

                    // Collect the bounds from the start of the immediate enclosing subline of the last LSCP
                    // to the hittested text cell.
                    AddValidTextBounds(
                        boundsList,
                        new TextBounds(
                            LSRun.RectUV(
                                position,
                                new LSPOINT(
                                    LSLineUToParagraphU(currentDistance),
                                    0
                                ),
                                new LSPOINT(
                                    LSLineUToParagraphU(
                                        GetDistanceInsideTextCell(
                                            lscpEnd,
                                            isCpEndTrailing,
                                            lastSublines,
                                            lastDepth,
                                            ref lastTextCell
                                            ) + lastTextCell.pointUvStartCell.x
                                        ),
                                    _metrics._height
                                ),
                                this
                            ),
                            Convert.LsTFlowToFlowDirection(lastSublines[lastDepth - 1].lstflowSubLine),
                            CalculateTextRunBounds(lscpCurrent, lscpEnd + 1)
                        )
                    );
                }

                if (bounds == null)
                {
                    Debug.Assert(boundsList != null);
                    if (boundsList.Count > 0)
                    {
                        if (collapsingSymbolSelected)
                        {
                            // add one more for collapsed symbol
                            AddValidTextBounds(boundsList, CreateCollapsingSymbolBounds());
                        }

                        bounds = new TextBounds[boundsList.Count];
                        for (int i = 0; i < boundsList.Count; i++)
                        {
                            bounds[i] = (TextBounds)boundsList[i];
                        }
                    }
                    else
                    {
                        // No non-zerowidth bounds detected, fallback to the position of first cp
                        // This can happen if hidden run is hittest'd.

                        int u =  LSLineUToParagraphU(
                            GetDistanceInsideTextCell(
                                lscpFirst,
                                isCpFirstTrailing,
                                firstSublines,
                                firstDepth,
                                ref firstTextCell
                                ) + firstTextCell.pointUvStartCell.x
                            );

                        bounds = new TextBounds[]
                        {
                            new TextBounds(
                                LSRun.RectUV(
                                    position,
                                    new LSPOINT(u, 0),
                                    new LSPOINT(u, _metrics._height),
                                    this
                                    ),
                                Convert.LsTFlowToFlowDirection(firstSublines[firstDepth - 1].lstflowSubLine),
                                null
                                )
                        };
                    }
                }

                return bounds;
            }