internal void GetShapeableText( CharacterBufferReference characterBufferReference, int stringLength, TextRunProperties textRunProperties, CultureInfo digitCulture, bool isRightToLeftParagraph, IList<TextShapeableSymbols> shapeableList, IShapeableTextCollector collector, TextFormattingMode textFormattingMode ) { SpanVector<int> cachedScaledTypefaceIndexSpans; int ichItem = 0; CharacterBufferRange unicodeString = new CharacterBufferRange( characterBufferReference, stringLength ); CultureInfo culture = textRunProperties.CultureInfo; IList<Span> spans; GCHandle gcHandle; IntPtr ptext = characterBufferReference.CharacterBuffer.PinAndGetCharacterPointer(characterBufferReference.OffsetToFirstChar, out gcHandle); // Contextual number substitution cannot be performed on the run level, since it depends // on context - nearest preceding strong character. For this reason, contextual number // substitutions has been already done (TextStore.CreateLSRunsUniformBidiLevel) and // digitCulture has been updated to reflect culture which is dependent on the context. // NumberSubstitutionMethod.AsCulture method can be resolved to Context, hence it also needs to be resolved to appropriate // not ambiguous method. // Both of those values (Context and AsCulture) are resolved to one of following: European, Traditional or NativeNational, // which can be safely handled by DWrite without getting context information. bool ignoreUserOverride; NumberSubstitutionMethod numberSubstitutionMethod = DigitState.GetResolvedSubstitutionMethod(textRunProperties, digitCulture, out ignoreUserOverride); // Itemize the text based on DWrite's text analysis for scripts and number substitution. unsafe { checked { spans = MS.Internal.Text.TextInterface.TextAnalyzer.Itemize( (ushort*)ptext.ToPointer(), (uint)stringLength, culture, MS.Internal.FontCache.DWriteFactory.Instance, isRightToLeftParagraph, digitCulture, ignoreUserOverride, (uint)numberSubstitutionMethod, ClassificationUtility.Instance, UnsafeNativeMethods.CreateTextAnalysisSink, UnsafeNativeMethods.GetScriptAnalysisList, UnsafeNativeMethods.GetNumberSubstitutionList, UnsafeNativeMethods.CreateTextAnalysisSource ); } } characterBufferReference.CharacterBuffer.UnpinCharacterPointer(gcHandle); SpanVector itemSpans = new SpanVector(null, new FrugalStructList<Span>((ICollection<Span>)spans)); cachedScaledTypefaceIndexSpans = new SpanVector<int>(-1); foreach(Span itemSpan in itemSpans) { MapItem( new CharacterBufferRange( unicodeString, ichItem, itemSpan.length ), culture, itemSpan, ref cachedScaledTypefaceIndexSpans, ichItem ); #if DEBUG ValidateMapResult( ichItem, itemSpan.length, ref cachedScaledTypefaceIndexSpans ); #endif ichItem += itemSpan.length; } Debug.Assert(ichItem == unicodeString.Length); // intersect item spans with shapeable spans to create span of shapeable runs int ich = 0; SpanRider itemSpanRider = new SpanRider(itemSpans); SpanRider<int> typefaceIndexSpanRider = new SpanRider<int>(cachedScaledTypefaceIndexSpans); while(ich < unicodeString.Length) { itemSpanRider.At(ich); typefaceIndexSpanRider.At(ich); int index = typefaceIndexSpanRider.CurrentValue; Debug.Assert(index >= 0); int cch = unicodeString.Length - ich; cch = Math.Min(cch, itemSpanRider.Length); cch = Math.Min(cch, typefaceIndexSpanRider.Length); ScaledShapeTypeface scaledShapeTypeface = _cachedScaledTypefaces[index]; collector.Add( shapeableList, new CharacterBufferRange( unicodeString, ich, cch ), textRunProperties, (MS.Internal.Text.TextInterface.ItemProps)itemSpanRider.CurrentElement, scaledShapeTypeface.ShapeTypeface, scaledShapeTypeface.ScaleInEm, scaledShapeTypeface.NullShape, textFormattingMode ); ich += cch; } }
/// <summary> /// Cache index to the list of scaled shapeable typeface /// </summary> private void CacheScaledTypefaceMap( CharacterBufferRange unicodeString, CultureInfo culture, CultureInfo digitCulture, SpanVector scaledTypefaceSpans, ref SpanVector<int> cachedScaledTypefaceIndexSpans, int ichItem ) { IntMap map; if (!_intMaps.TryGetValue(culture, out map)) { map = new IntMap(); _intMaps.Add(culture, map); } DigitMap digitMap = new DigitMap(digitCulture); SpanRider typefaceSpanRider = new SpanRider(scaledTypefaceSpans); int ich = 0; while(ich < unicodeString.Length) { typefaceSpanRider.At(ich); int cch = Math.Min(unicodeString.Length - ich, typefaceSpanRider.Length); int index = IndexOfScaledTypeface((ScaledShapeTypeface)typefaceSpanRider.CurrentElement); Debug.Assert(index >= 0, "Invalid scaled shapeable typeface index spans"); cachedScaledTypefaceIndexSpans.Set(ichItem + ich, cch, index); // we keep index + 1 in the map, so that we leave map entry zero // to indicate uninitialized entry. index++; int sizeofChar; for (int c = 0; c < cch; c += sizeofChar) { int ch = digitMap[ Classification.UnicodeScalar( new CharacterBufferRange(unicodeString, ich + c, unicodeString.Length - ich - c), out sizeofChar ) ]; // only cache typeface map index for base characters if(!Classification.IsCombining(ch) && !Classification.IsJoiner(ch)) { // Dump values of local variables when the condition fails for better debuggability. // We use "if" to avoid the expensive string.Format() in normal case. if (map[ch] != 0 && map[ch] != index) { Invariant.Assert( false, string.Format( CultureInfo.InvariantCulture, "shapeable cache stores conflicting info, ch = {0}, map[ch] = {1}, index = {2}", ch, map[ch], index ) ); } map[ch] = (ushort)index; } } ich += cch; } }
private unsafe void ValidateMapResult( int ichRange, int cchRange, ref SpanVector<int> cachedScaledTypefaceIndexSpans ) { int ich = 0; SpanRider<int> typefaceIndexSpanRider = new SpanRider<int>(cachedScaledTypefaceIndexSpans); while(ich < cchRange) { typefaceIndexSpanRider.At(ichRange + ich); if((int)typefaceIndexSpanRider.CurrentValue < 0) { Debug.Assert(false, "Invalid font face spans"); return; } int cch = Math.Min(cchRange - ich, typefaceIndexSpanRider.Length); ich += cch; } }
/// <summary> /// Map internal LSCP to text source cp /// </summary> internal int GetExternalCp(int lscp) { if (lscp >= _metrics._lscpLim) { if (_collapsedRange != null) return _collapsedRange.TextSourceCharacterIndex; return _cpFirst + _metrics._cchLength; } int offsetToFirstCp; SpanRider plsrunSpanRider = new SpanRider(_plsrunVector); // skip lscp until we find one with valid map do { plsrunSpanRider.At(lscp - _cpFirst); offsetToFirstCp = GetRun(((Plsrun)plsrunSpanRider.CurrentElement)).OffsetToFirstCp; } while(offsetToFirstCp < 0 && ++lscp < _metrics._lscpLim); return offsetToFirstCp + lscp - plsrunSpanRider.CurrentSpanStart; }
/// <summary> /// Compute bounds of runs within the specified range of lscp /// </summary> private IList<TextRunBounds> CalculateTextRunBounds(int lscpFirst, int lscpEnd) { if (lscpEnd <= lscpFirst) { // It is possible that we'll get a legitimate case when lscpFirst is // actually greater. That's what happen when the client hittest a hidden // run that follows a reverse block. Since it is a hidden run, LS has // to yield the closest non-hidden place which may be the run preceding // the hidden text. (wchao, PS bug #930976) return null; } int lscp = lscpFirst; int cchLeft = lscpEnd - lscpFirst; SpanRider plsrunSpanRider = new SpanRider(_plsrunVector); Point position = new Point(0, 0); IList<TextRunBounds> boundsList = new List<TextRunBounds>(2); while(cchLeft > 0) { plsrunSpanRider.At(lscp - _cpFirst); Plsrun plsrun = (Plsrun)plsrunSpanRider.CurrentElement; int cch = Math.Min(plsrunSpanRider.Length, cchLeft); if(TextStore.IsContent(plsrun)) { LSRun lsrun = GetRun(plsrun); if( lsrun.Type == Plsrun.Text || lsrun.Type == Plsrun.InlineObject) { int cp = GetExternalCp(lscp); int cchBounds = cch; if ( HasCollapsed && _collapsedRange != null && cp <= _collapsedRange.TextSourceCharacterIndex && cp + cchBounds >= _collapsedRange.TextSourceCharacterIndex && cp + cchBounds < _collapsedRange.TextSourceCharacterIndex + _collapsedRange.Length) { // Limit the run bounds to only non-collapsed text, // we deal with collapsed text separately as it might have different flow direction. cchBounds = _collapsedRange.TextSourceCharacterIndex - cp; } if (cchBounds > 0) { TextRunBounds bounds = new TextRunBounds( LSRun.RectUV( position, new LSPOINT( LSLineUToParagraphU( DistanceFromCharacterHit(new CharacterHit(cp, 0)) ), _metrics._baselineOffset - lsrun.BaselineOffset + lsrun.BaselineMoveOffset ), new LSPOINT( LSLineUToParagraphU( DistanceFromCharacterHit(new CharacterHit(cp + cchBounds - 1, 1)) ), _metrics._baselineOffset - lsrun.BaselineOffset + lsrun.BaselineMoveOffset + lsrun.Height ), this ), cp, cp + cchBounds, lsrun.TextRun ); boundsList.Add(bounds); } } } cchLeft -= cch; lscp += cch; } return boundsList.Count > 0 ? boundsList : null; }
/// <summary> /// Search from the given lscp (inclusive) towards the specified direction for the /// closest navigable cp. Return true is one such cp is found, false otherwise. /// </summary> private bool FindNextOrPreviousVisibleCp( int lscp, CaretDirection direction, out int lscpVisisble ) { lscpVisisble = lscp; SpanRider plsrunSpanRider = new SpanRider(_plsrunVector); if (direction == CaretDirection.Forward) { while (lscpVisisble < _metrics._lscpLim) { plsrunSpanRider.At(lscpVisisble - _cpFirst); LSRun run = GetRun((Plsrun) plsrunSpanRider.CurrentElement); // When scanning forward, only trailine edges of visiable content are navigable. if (run.IsVisible) { return true; } lscpVisisble += plsrunSpanRider.Length; // move to start of next span } } else { Debug.Assert(direction == CaretDirection.Backward || direction == CaretDirection.Backspace); // lscpCurrent can be right after the end of the line, we snap it back to be at the end of the line. lscpVisisble = Math.Min(lscpVisisble, _metrics._lscpLim - 1); while (lscpVisisble >= _cpFirst) { plsrunSpanRider.At(lscpVisisble - _cpFirst); LSRun run = GetRun((Plsrun) plsrunSpanRider.CurrentElement); // When scanning backward, visiable content has caret stop at its leading edge. if (run.IsVisible) { return true; } // When scanning backward, the newline sequence has caret stop at its leading edge. if (run.IsNewline) { // set navigable cp at the start of newline sequence. lscpVisisble = _cpFirst + plsrunSpanRider.CurrentSpanStart; return true; } lscpVisisble = _cpFirst + plsrunSpanRider.CurrentSpanStart - 1; // move to the end of previous span } } lscpVisisble = lscp; return false; }