// GetText handler for text runs. private static bool WalkTextRun(ITextPointer navigator, ITextPointer limit, char[] text, int cchReq, ref int charsCopied, UnsafeNativeMethods.TS_RUNINFO[] runInfo, int cRunInfoReq, ref int cRunInfoRcv) { int runCount; int offset; bool hitLimit; Invariant.Assert(navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text); Invariant.Assert(limit == null || navigator.CompareTo(limit) <= 0); hitLimit = false; if (cchReq > 0) { runCount = TextPointerBase.GetTextWithLimit(navigator, LogicalDirection.Forward, text, charsCopied, Math.Min(cchReq, text.Length - charsCopied), limit); navigator.MoveByOffset(runCount); charsCopied += runCount; hitLimit = (text.Length == charsCopied) || (limit != null && navigator.CompareTo(limit) == 0); } else { // Caller doesn't want text, just run info. // Advance the navigator. runCount = navigator.GetTextRunLength(LogicalDirection.Forward); navigator.MoveToNextContextPosition(LogicalDirection.Forward); // If the caller passed in a non-null limit, backup to the limit if // we've passed it. if (limit != null) { if (navigator.CompareTo(limit) >= 0) { offset = limit.GetOffsetToPosition(navigator); Invariant.Assert(offset >= 0 && offset <= runCount, "Bogus offset -- extends past run!"); runCount -= offset; navigator.MoveToPosition(limit); hitLimit = true; } } } if (cRunInfoReq > 0 && runCount > 0) { // Be sure to merge this text run with the previous run, if they are both text runs. // (A good robustness fix would be to make cicero handle this, if we ever get the chance.) if (cRunInfoRcv > 0 && runInfo[cRunInfoRcv - 1].type == UnsafeNativeMethods.TsRunType.TS_RT_PLAIN) { runInfo[cRunInfoRcv - 1].count += runCount; } else { runInfo[cRunInfoRcv].count = runCount; runInfo[cRunInfoRcv].type = UnsafeNativeMethods.TsRunType.TS_RT_PLAIN; cRunInfoRcv++; } } return hitLimit; }
/// <summary> /// Gets start and end offset for a text segment but clamps those values to the start and end /// of a given element. This way if a large text range is being resolved on a node that only contains /// a portion of the text range (such as a paragraph) the result only includes the content in that node. /// </summary> private void GetTextSegmentValues(TextSegment segment, ITextPointer elementStart, ITextPointer elementEnd, out int startOffset, out int endOffset) { startOffset = 0; endOffset = 0; if (elementStart.CompareTo(segment.Start) >= 0) { // segment starts before the start of the element startOffset = 0; } else { startOffset = elementStart.GetOffsetToPosition(segment.Start); } if (elementEnd.CompareTo(segment.End) >= 0) { endOffset = elementStart.GetOffsetToPosition(segment.End); } else { // segment ends after the end of the element endOffset = elementStart.GetOffsetToPosition(elementEnd); } }
// Like GetText, excepts also accepts a limit parameter -- no text is returned past // this second position. // limit may be null, in which case it is ignored. internal static int GetTextWithLimit(ITextPointer thisPointer, LogicalDirection direction, char[] textBuffer, int startIndex, int count, ITextPointer limit) { int charsCopied; if (limit == null) { // No limit, just call GetText. charsCopied = thisPointer.GetTextInRun(direction, textBuffer, startIndex, count); } else if (direction == LogicalDirection.Forward && limit.CompareTo(thisPointer) <= 0) { // Limit completely blocks the read. charsCopied = 0; } else if (direction == LogicalDirection.Backward && limit.CompareTo(thisPointer) >= 0) { // Limit completely blocks the read. charsCopied = 0; } else { int maxCount; // Get an upper bound on the amount of text to copy. // Since GetText always stops on non-text boundaries, it's // ok if the count too high, it will get truncated anyways. if (direction == LogicalDirection.Forward) { maxCount = Math.Min(count, thisPointer.GetOffsetToPosition(limit)); } else { maxCount = Math.Min(count, limit.GetOffsetToPosition(thisPointer)); } maxCount = Math.Min(count, maxCount); charsCopied = thisPointer.GetTextInRun(direction, textBuffer, startIndex, maxCount); } return charsCopied; }
// Finds the next dirty range following searchStart, without considering // the current caret or IME composition. private void GetNextScanRangeRaw(ITextPointer searchStart, out ITextPointer start, out ITextPointer end) { Invariant.Assert(searchStart != null); start = null; end = null; // Grab the first dirty range. _statusTable.GetFirstDirtyRange(searchStart, out start, out end); if (start != null) { Invariant.Assert(start.CompareTo(end) < 0); // Cap the block size by a constant. if (start.GetOffsetToPosition(end) > MaxScanBlockSize) { end = start.CreatePointer(MaxScanBlockSize); } // Ensure the block has constant language. XmlLanguage language = GetCurrentLanguage(start); end = GetNextLanguageTransition(start, LogicalDirection.Forward, language, end); Invariant.Assert(start.CompareTo(end) < 0); } }
// Creates a new instance. // contextStart/End refer to the whole run of text. // contentStart/End are a subset of the text, which is what // the engine will actually tag with errors. // The space between context and content is used by the engine // to correctly analyze multiple word phrase like "Los Angeles" // that could otherwise be truncated and incorrectly tagged. internal TextMap(ITextPointer contextStart, ITextPointer contextEnd, ITextPointer contentStart, ITextPointer contentEnd) { ITextPointer position; int maxChars; int inlineCount; int runCount; int i; int distance; Invariant.Assert(contextStart.CompareTo(contentStart) <= 0); Invariant.Assert(contextEnd.CompareTo(contentEnd) >= 0); _basePosition = contextStart.GetFrozenPointer(LogicalDirection.Backward); position = contextStart.CreatePointer(); maxChars = contextStart.GetOffsetToPosition(contextEnd); _text = new char[maxChars]; _positionMap = new int[maxChars+1]; _textLength = 0; inlineCount = 0; _contentStartOffset = 0; _contentEndOffset = 0; // Iterate over the run, building up a matching plain text buffer // and a table that tells us how to map back to the original text. while (position.CompareTo(contextEnd) < 0) { if (position.CompareTo(contentStart) == 0) { _contentStartOffset = _textLength; } if (position.CompareTo(contentEnd) == 0) { _contentEndOffset = _textLength; } switch (position.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: runCount = position.GetTextRunLength(LogicalDirection.Forward); runCount = Math.Min(runCount, _text.Length - _textLength); runCount = Math.Min(runCount, position.GetOffsetToPosition(contextEnd)); position.GetTextInRun(LogicalDirection.Forward, _text, _textLength, runCount); for (i = _textLength; i < _textLength + runCount; i++) { _positionMap[i] = i + inlineCount; } distance = position.GetOffsetToPosition(contentStart); if (distance >= 0 && distance <= runCount) { _contentStartOffset = _textLength + position.GetOffsetToPosition(contentStart); } distance = position.GetOffsetToPosition(contentEnd); if (distance >= 0 && distance <= runCount) { _contentEndOffset = _textLength + position.GetOffsetToPosition(contentEnd); } position.MoveByOffset(runCount); _textLength += runCount; break; case TextPointerContext.ElementStart: case TextPointerContext.ElementEnd: if (IsAdjacentToFormatElement(position)) { // Filter out formatting tags from the plain text. inlineCount++; } else { // Stick in a word break to account for the block element. _text[_textLength] = ' '; _positionMap[_textLength] = _textLength + inlineCount; _textLength++; } position.MoveToNextContextPosition(LogicalDirection.Forward); break; case TextPointerContext.EmbeddedElement: _text[_textLength] = '\xf8ff'; // Unicode private use. _positionMap[_textLength] = _textLength + inlineCount; _textLength++; position.MoveToNextContextPosition(LogicalDirection.Forward); break; } } if (position.CompareTo(contentEnd) == 0) { _contentEndOffset = _textLength; } if (_textLength > 0) { _positionMap[_textLength] = _positionMap[_textLength - 1] + 1; } else { _positionMap[0] = 0; } Invariant.Assert(_contentStartOffset <= _contentEndOffset); }
/// <summary> /// Set the find text content from reading the text on the current text position. /// </summary> /// <returns> /// Returns the number of characters actually loaded into the findText array. /// </returns> private static int SetFindTextAndFindTextPositionMap( ITextPointer startPosition, ITextPointer endPosition, ITextPointer navigator, LogicalDirection direction, bool matchLast, char[] findText, int[] findTextPositionMap) { Invariant.Assert(startPosition.CompareTo(navigator) <= 0); Invariant.Assert(endPosition.CompareTo(navigator) >= 0); int runCount; int inlineCount = 0; int findTextLength = 0; // Set the first offset which is zero on TextBufferSize + 1 location of // the text position map in case of the backward searching if (matchLast && findTextLength == 0) { findTextPositionMap[findTextPositionMap.Length - 1] = 0; } while ((matchLast ? startPosition.CompareTo(navigator) : navigator.CompareTo(endPosition)) < 0) { switch (navigator.GetPointerContext(direction)) { case TextPointerContext.Text: runCount = navigator.GetTextRunLength(direction); runCount = Math.Min(runCount, findText.Length - findTextLength); if (!matchLast) { runCount = Math.Min(runCount, navigator.GetOffsetToPosition(endPosition)); navigator.GetTextInRun(direction, findText, findTextLength, runCount); for (int i = findTextLength; i < findTextLength + runCount; i++) { findTextPositionMap[i] = i + inlineCount; } } else { runCount = Math.Min(runCount, startPosition.GetOffsetToPosition(navigator)); navigator.GetTextInRun( direction, findText, findText.Length - findTextLength - runCount, runCount); // Set the text offest for the amount of runCount from the last index // of text position map int mapIndex = findText.Length - findTextLength - 1; for (int i = findTextLength; i < findTextLength + runCount; i++) { findTextPositionMap[mapIndex--] = i + inlineCount + 1; } } // Move the navigator position for the amount of runCount navigator.MoveByOffset(matchLast ? - runCount : runCount); findTextLength += runCount; break; case TextPointerContext.None: case TextPointerContext.ElementStart: case TextPointerContext.ElementEnd: if (IsAdjacentToFormatElement(navigator, direction)) { // Filter out formatting tags since find text content is plain. inlineCount++; } else { if (!matchLast) { // Stick in a line break to account for the block element. findText[findTextLength] = '\n'; findTextPositionMap[findTextLength] = findTextLength + inlineCount; findTextLength++; } else { // Increse the find text length first since adding text and map reversely findTextLength++; // Stick in a line break to account for the block element and // add text offset on the last index of text position map findText[findText.Length - findTextLength] = '\n'; findTextPositionMap[findText.Length - findTextLength] = findTextLength + inlineCount; } } navigator.MoveToNextContextPosition(direction); break; case TextPointerContext.EmbeddedElement: if (!matchLast) { findText[findTextLength] = '\xf8ff'; // Unicode private use. findTextPositionMap[findTextLength] = findTextLength + inlineCount; findTextLength++; } else { // Increse the find text length first since adding text and map reversely findTextLength++; // Set the private unicode value and text offset findText[findText.Length - findTextLength] = '\xf8ff'; findTextPositionMap[findText.Length - findTextLength] = findTextLength + inlineCount; } navigator.MoveToNextContextPosition(direction); break; } if (findTextLength >= findText.Length) { break; } } // Complete the adding the find text position to the position map for only the forward finding. // The backward finding(matchLast) is already added initially as the zero offset at the end of // text position map. if (!matchLast) { if (findTextLength > 0) { findTextPositionMap[findTextLength] = findTextPositionMap[findTextLength - 1] + 1; } else { findTextPositionMap[0] = 0; } } return findTextLength; }
// Returns true iff there is a character in the specificed direction adjacent to a // position which is classified as a separator. This is useful in detecting word breaks. private static bool HasNeighboringSeparatorChar(ITextPointer position, LogicalDirection direction) { ITextPointer nextPosition = position.GetNextInsertionPosition(direction); if (nextPosition == null) { return true; } if (position.CompareTo(nextPosition) > 0) { ITextPointer temp = position; position = nextPosition; nextPosition = temp; } int maxCharCount = position.GetOffsetToPosition(nextPosition); char[] findText = new char[maxCharCount]; int []findTextPositionMap = new int[maxCharCount + 1]; int findTextLength; findTextLength = SetFindTextAndFindTextPositionMap( position, nextPosition, position.CreatePointer() /* need unfrozen pointer */, LogicalDirection.Forward, false /* matchLast */, findText, findTextPositionMap); if (findTextLength == 0) { return true; } bool hasNeighboringSeparatorChar; if (direction == LogicalDirection.Forward) { hasNeighboringSeparatorChar = IsSeparatorChar(findText[0]); } else { hasNeighboringSeparatorChar = IsSeparatorChar(findText[findTextLength-1]); } return hasNeighboringSeparatorChar; }