private void QueryLineCpPpoint( int lscpQuery, LsQSubInfo[] subLineInfo, out int actualDepthQuery, out LsTextCell lsTextCell ) { Debug.Assert(_ploline.Value != IntPtr.Zero); LsErr lserr = LsErr.None; lsTextCell = new LsTextCell(); // Never hit LS with any LSCP beyond its last, the result is unreliable and varies between drops. int lscpValidQuery = (lscpQuery < _metrics._lscpLim ? lscpQuery : _metrics._lscpLim - 1); unsafe { fixed(LsQSubInfo* plsqsubl = subLineInfo) { lserr = UnsafeNativeMethods.LoQueryLineCpPpoint( _ploline.Value, lscpValidQuery, subLineInfo.Length, (System.IntPtr)plsqsubl, out actualDepthQuery, out lsTextCell ); } } if(lserr != LsErr.None) { TextFormatterContext.ThrowExceptionFromLsError(SR.Get(SRID.QueryLineFailure, lserr), lserr); } if (lsTextCell.lscpEndCell < lsTextCell.lscpStartCell) { // When hit-testing is done on a generated hyphen of a hyphenated word, LS can only tell // the start LSCP and not the end LSCP. Argurably this is LS bug. In such situation they // should assume the end LSCP being the last LSCP of the line. // // However our code assumes that LS must tell both and the text cell must have size greater // than one codepoint. We count on that to reliably advance the caret position. // // The LSPTS bug#1005 has been filed and while we are still debating, we need to unblock // ourselves. What we can do is to assume that the next caret stop in this case is always // the next codepoint. lsTextCell.lscpEndCell = lsTextCell.lscpStartCell; } }
/// <summary> /// Given a specified current character index, calculate the character index /// to the closest caret stop before or at the current index; and the number /// of codepoints from the closest caret stop to the next caret stop. /// </summary> private bool GetNextOrPreviousCaretStop( int currentIndex, CaretDirection direction, out int caretStopIndex, out int offsetToNextCaretStopIndex ) { caretStopIndex = currentIndex; offsetToNextCaretStopIndex = 0; if ( HasCollapsed && _collapsedRange != null && currentIndex >= _collapsedRange.TextSourceCharacterIndex ) { // current index is within collapsed range, caretStopIndex = _collapsedRange.TextSourceCharacterIndex; if (currentIndex < _collapsedRange.TextSourceCharacterIndex + _collapsedRange.Length) offsetToNextCaretStopIndex = _collapsedRange.Length; return true; } LsQSubInfo[] sublineInfo = new LsQSubInfo[_depthQueryMax]; LsTextCell lsTextCell = new LsTextCell(); int lscpVisisble = GetInternalCp(currentIndex); bool found = FindNextOrPreviousVisibleCp(lscpVisisble, direction, out lscpVisisble); if (!found) { return false; // there is no caret stop anymore in the given direction. } int actualSublineCount; QueryLineCpPpoint( lscpVisisble, sublineInfo, out actualSublineCount, out lsTextCell ); // Locate the current caret stop caretStopIndex = GetExternalCp(lsTextCell.lscpStartCell); if ( actualSublineCount > 0 && lscpVisisble >= lsTextCell.lscpStartCell && lscpVisisble <= lsTextCell.lscpEndCell ) { // the last subline contains the run that owns the querying lscp LSRun lsrun = GetRun((Plsrun)sublineInfo[actualSublineCount - 1].plsrun); if (lsrun.IsHitTestable) { if ( lsrun.HasExtendedCharacter || (direction != CaretDirection.Backspace && lsrun.NeedsCaretInfo) ) { // LsTextCell.lscpEndCell is the index to the last lscp still in the cell. // The number of LSCP within the text cell is equal to the number of CP. offsetToNextCaretStopIndex = lsTextCell.lscpEndCell + 1 - lsTextCell.lscpStartCell; } else { // caret stops before every codepoint caretStopIndex = GetExternalCp(lscpVisisble); offsetToNextCaretStopIndex = 1; } } else { // run is not hit-testable, caret navigation is not allowed in the run, // the next caret stop is therefore either at the end of the run or the end of the line whichever reached first. offsetToNextCaretStopIndex = Math.Min(Length, lsrun.Length - caretStopIndex + lsrun.OffsetToFirstCp + _cpFirst); } } return true; }
internal static extern LsErr LoQueryLinePointPcp( IntPtr ploline, ref LSPOINT ptQuery, // use POINT as POINTUV int depthQueryMax, IntPtr pSubLineInfo, // passing raw pinned pointer for out array out int actualDepthQuery, out LsTextCell lsTextCell );
/// <summary> /// Get distance from the start of text cell to the specified lscp /// </summary> private int GetDistanceInsideTextCell( int lscpCurrent, bool isTrailing, LsQSubInfo[] sublineInfo, int actualSublineCount, ref LsTextCell lsTextCell ) { int distanceInCell = 0; // All the UV coordinate in subline are in main direction. If the last subline where // we hittest runs in the opposite direction, the logical advance from text cell start cp // will be negative value. int direction = (sublineInfo[actualSublineCount - 1].lstflowSubLine == sublineInfo[0].lstflowSubLine) ? 1 : -1; // LsTextCell.lscpEndCell is the index to the last lscp still in the cell. // The number of LSCP within the text cell is equal to the number of CP. // // Assuming caret stops at every codepoint in the run. int caretStopCount = lsTextCell.lscpEndCell + 1 - lsTextCell.lscpStartCell; int codepointsFromStartCell = lscpCurrent - lsTextCell.lscpStartCell; // the last subline contains the run that owns the querying lscp LSRun lsrun = GetRun((Plsrun)sublineInfo[actualSublineCount - 1].plsrun); if ( lsrun.IsHitTestable && ( lsrun.HasExtendedCharacter || lsrun.NeedsCaretInfo) ) { // A hit-testable run with caret stops at every cluster boundaries, // e.g. run with combining mark, with extended characters or complex scripts such as Thai caretStopCount = 1; } Invariant.Assert(caretStopCount > 0); int wholeAdvance = lsTextCell.dupCell / caretStopCount; int remainingAdvance = lsTextCell.dupCell % caretStopCount; for (int i = 1; i <= caretStopCount; i++) { int caretAdvance = wholeAdvance; if (remainingAdvance > 0) { caretAdvance++; remainingAdvance--; } if (codepointsFromStartCell < i) { if (isTrailing) { // hit-test at the trailing edge of the current caret stop, include the current caret advance return (distanceInCell + caretAdvance) * direction; } // hit-test at the leading edge of the current caret stop, return the accumulated distance return distanceInCell * direction; } distanceInCell += caretAdvance; } // hit-test beyond the last caret stop, return the total accumated distance up to the trailing edge of the last caret stop. return distanceInCell * direction; }
internal static extern LsErr LoQueryLineCpPpoint( IntPtr ploline, int lscpQuery, int depthQueryMax, IntPtr pSubLineInfo, // passing raw pinned pointer for out array out int actualDepthQuery, out LsTextCell lsTextCell );