/// <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; }