Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / MS / Internal / TextFormatting / TextStore.cs / 1305600 / TextStore.cs
//------------------------------------------------------------------------ // // Microsoft Windows Client Platform // Copyright (C) Microsoft Corporation // // File: TextStore.cs // // Contents: FullTextLine text store // // Created: 10-3-2002 Worachai Chaoweeraprasit (wchao) // //----------------------------------------------------------------------- using System; using System.Text; using System.Globalization; using System.Windows; using System.Windows.Media; using System.Windows.Media.TextFormatting; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using MS.Internal.Shaping; using MS.Internal.Generic; using System.Security; using System.Security.Permissions; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace MS.Internal.TextFormatting { ////// FullTextLine text store /// ////// /// Text store produces and keeps 'lsrun' of different type. Each type has its own /// characteristics and each lsrun involves with three different 'character length' /// values created and used for different purpose by different component. /// /// plsrunSpan.length: character length created by text store used by LS. /// lsrun.Length: character length assigned and used by TextSource. /// lsrun.StringLength: character length created by text store used by bidi algorithm. /// /// /// plsrunSpan.length lsrun.Length lsrun.StringLength /// CloseAnchor 1 0 1 /// Reverse 1 0 1 /// FakeLineBreak 1 0 1 /// FormatAnchor 1 1 1 /// Hidden n n 1 /// Text n n n /// InlineObject 1 n 1 /// LineBreak 1 n 1 /// /// FakeLineBreak is used to chop off a line by simulating a line break when /// none is actually present in the backing store. This is done as a security /// mitigation for malicious text where we might otherwise spend too much time /// formatting a line. The IsForceBreakRequired method contains the mitigation /// logic; see also the comment for MaxCharactersPerLine. /// /// internal class TextStore { private FormatSettings _settings; // format settings private int _lscpFirstValue; // first lscp value private int _cpFirst; // store first cp (both cp and lscp start out the same) private int _lscchUpTo; // number of lscp resolved private int _cchUpTo; // number of cp resolved private int _cchEol; // number of chars for end-of-line mark private int _accNominalWidthSoFar; // accumulated nominal width so far private int _accTextLengthSoFar; // accumulated count of text characters so far private NumberContext _numberContext; // cached number context for contextual digit substitution private int _cpNumberContext; // cp at which _numberContext is valid private SpanVector _plsrunVector; private SpanPosition _plsrunVectorLatestPosition; private ArrayList _lsrunList; // lsrun list private BidiState _bidiState; // (defer initialization until FetchRun) private TextModifierScope _modifierScope; // top-most frame of the text modifier stack, or null private int _formatWidth; // formatting width LS sees private SpanVector _textObjectMetricsVector; // inline object cache internal static LSRun[] ControlRuns; // Control text runs e.g. Bidi reversal ////// Initialize all static members /// static TextStore() { EscStringInfo esc = new EscStringInfo(); UnsafeNativeMethods.LoGetEscString(ref esc); ControlRuns = new LSRun[3]; ControlRuns[0] = new LSRun(Plsrun.CloseAnchor, esc.szObjectTerminator); ControlRuns[1] = new LSRun(Plsrun.Reverse, esc.szObjectReplacement); ControlRuns[2] = new LSRun(Plsrun.FakeLineBreak, esc.szLineSeparator); PwchNbsp = esc.szNbsp; PwchHidden = esc.szHidden; PwchParaSeparator = esc.szParaSeparator; PwchLineSeparator = esc.szLineSeparator; PwchObjectReplacement = esc.szObjectReplacement; PwchObjectTerminator = esc.szObjectTerminator; } ////// Constructing an intermediate text store for FullTextLine /// /// text formatting settings /// first cp of the line /// lscp first value /// formatting width LS sees public TextStore( FormatSettings settings, int cpFirst, int lscpFirstValue, int formatWidth ) { _settings = settings; _formatWidth = formatWidth; _cpFirst = cpFirst; _lscpFirstValue = lscpFirstValue; _lsrunList = new ArrayList(2); _plsrunVector = new SpanVector(null); _plsrunVectorLatestPosition = new SpanPosition(); // Recreate the stack of text modifiers if there is one. TextLineBreak previousLineBreak = settings.PreviousLineBreak; if ( previousLineBreak != null && previousLineBreak.TextModifierScope != null) { _modifierScope = previousLineBreak.TextModifierScope.CloneStack(); // Construct bidi state from input settings and modifier scopes _bidiState = new BidiState(_settings, _cpFirst, _modifierScope); } } ////// Fetch lsrun at the specified LSCP /// /// lscp to fetch /// The layout mode /// Whether the text in the run should be sideways /// plsrun of lsrun being fetched /// offset from the start of the LSRun to the specified lscp /// distance from the specified lscp to the end of the LSRun ///lsrun being fetched internal LSRun FetchLSRun( int lscpFetch, TextFormattingMode textFormattingMode, bool isSideways, out Plsrun plsrun, out int lsrunOffset, out int lsrunLength ) { lscpFetch -= _lscpFirstValue; Invariant.Assert(lscpFetch >= _cpFirst); if (_cpFirst + _lscchUpTo <= lscpFetch) { ushort charFlagsSoFar = 0; ushort bidiCharFlagsSoFar = 0; int cchResolved = 0; int cchFetched = _cchUpTo; int cch = 0; int cchText = 0; SpanVector runInfoVector = new SpanVector(null); SpanVector textEffectsVector = new SpanVector(null); byte[] bidiLevels = null; int lastBidiLevel = GetLastLevel(); // Fetch runs until enough characters get resolved by bidi algorithm do { // Read runs up ahead to the point where accumulated run width exceeds the upper limit value. // We do this to optimize the cost of breaking down lsruns by doing as much as we can in // one go, thus generating smaller setup cost of run fetching. TextRunInfo runInfo; do { runInfo = FetchTextRun(_cpFirst + cchFetched); if (runInfo == null) { // no more content to fetch break; } if ( _bidiState == null && ( IsDirectionalModifier(runInfo.TextRun as TextModifier) || IsEndOfDirectionalModifier(runInfo) ) ) { // When directional embedding TextModifier or corresponding TextEndOfSegment // is encountered, we need to do bidi analysis to correctly update the bidi state. // We create a bidi state to trigger bidi analysis. _bidiState = new BidiState(_settings, _cpFirst); } int stringLength = runInfo.StringLength; if(runInfo.TextRun is ITextSymbols) { // Let stopMask specify which types of characters need to be isolated in special runs. // Let bidiMask specify which types of characters require bidi analysis. ushort stopMask; ushort bidiMask; if (!runInfo.IsSymbol) { // It's an ordinary Unicode font. Isolate various line breaks and format anchor. stopMask = (ushort)(CharacterAttributeFlags.CharacterLineBreak | CharacterAttributeFlags.CharacterParaBreak | CharacterAttributeFlags.CharacterFormatAnchor); // Mask of character flags that require us to perform bidi analysis. bidiMask = (ushort)(CharacterAttributeFlags.CharacterRTL); } else { // It's a non-Unicode font, meaning code points have non-standard meanings. The only // characters we recognize as line breaks are LF (0x0A) and CR (0x0D). stopMask = (ushort)(CharacterAttributeFlags.CharacterCRLF | CharacterAttributeFlags.CharacterFormatAnchor); // Layout is always left-to-right for non-Unicode fonts. bidiMask = 0; } // Scan until end-of-run or one of the characters specified by stopMask. The accumulated // flags of the characters we advanced past are stored in charFlags. ushort charFlags; stringLength = Classification.AdvanceUntilUTF16( runInfo.CharacterBuffer, runInfo.OffsetToFirstChar, runInfo.Length, // text is chopped at run length not string length stopMask, out charFlags ); // If it's a non-Unicode font we may have advanced past line break characters, // but if so we don't want to treat them as such. charFlags &= (ushort)~(CharacterAttributeFlags.CharacterLineBreak | CharacterAttributeFlags.CharacterParaBreak); if(stringLength <= 0) { // There are special characters such as various linebreaks or format anchor // character in the middle of text stream. We isolate such characters into // a separate run and treat them accordingly. runInfo = CreateSpecialRunFromTextContent(runInfo, cchFetched); stringLength = runInfo.StringLength; charFlags = runInfo.CharacterAttributeFlags; Debug.Assert(stringLength > 0 && runInfo.Length > 0); } else if(stringLength != runInfo.Length) { // shorten the run length if the character string is being cut short runInfo.Length = stringLength; } runInfo.CharacterAttributeFlags |= charFlags; charFlagsSoFar |= charFlags; bidiCharFlagsSoFar |= (ushort)(charFlags & bidiMask); cchText += stringLength; } _accNominalWidthSoFar += runInfo.GetRoughWidth(TextFormatterImp.ToIdeal); // store up the run info in a span indexed by actual character index runInfoVector.SetReference(cch, stringLength, runInfo); TextEffectCollection textEffects = (runInfo.Properties != null) ? runInfo.Properties.TextEffects : null; if (textEffects != null && textEffects.Count != 0) { SetTextEffectsVector(textEffectsVector, cch, runInfo, textEffects); } cch += stringLength; cchFetched += runInfo.Length; } while( _accNominalWidthSoFar < _formatWidth && !runInfo.IsEndOfLine && !IsNewline(charFlagsSoFar) && _accTextLengthSoFar + cchText <= MaxCharactersPerLine ); // if bidi is detected, resolve all fetched runs if ( lastBidiLevel > 0 || bidiCharFlagsSoFar != 0 || _bidiState != null ) { cchResolved = BidiAnalyze(runInfoVector, cch, out bidiLevels); // for security reasons, limit how far we'll scan ahead to resolve bidi levels if (cchResolved == 0 && _accTextLengthSoFar + cchText >= MaxCharactersPerLine) { cchResolved = cch; bidiLevels = null; } } else { cchResolved = cch; } } while(cchResolved <= 0); Debug.Assert( runInfoVector != null && ( bidiLevels == null || cchResolved <= bidiLevels.Length) ); bool forceBreak = IsForceBreakRequired(runInfoVector, ref cchResolved); if(bidiLevels == null) { // no bidi detected, all characters are left-to-right CreateLSRunsUniformBidiLevel( runInfoVector, textEffectsVector, _cchUpTo, 0, cchResolved, 0, // uniformBidiLevel textFormattingMode, isSideways, ref lastBidiLevel ); } else { int runInfoFirstCp = _cchUpTo; int ichUniform = 0; while(ichUniform < cchResolved) { int cchUniform = 1; int uniformBidiLevel = bidiLevels[ichUniform]; while( ichUniform + cchUniform < cchResolved && bidiLevels[ichUniform + cchUniform] == uniformBidiLevel) { cchUniform++; } // create lsruns within a range of uniform level CreateLSRunsUniformBidiLevel( runInfoVector, textEffectsVector, runInfoFirstCp, ichUniform, cchUniform, uniformBidiLevel, textFormattingMode, isSideways, ref lastBidiLevel ); ichUniform += cchUniform; } } if (forceBreak) { // close reverse runs if (lastBidiLevel != 0) { lastBidiLevel = CreateReverseLSRuns(BaseBidiLevel, lastBidiLevel); } // add a fake linebreak _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, Plsrun.FakeLineBreak, _plsrunVectorLatestPosition); _lscchUpTo += 1; } } // lsrun at the specified lscp was created, just grab it and go return GrabLSRun( lscpFetch, out plsrun, out lsrunOffset, out lsrunLength ); } ////// Wrapper to TextRun fetching from the cache /// internal TextRunInfo FetchTextRun(int cpFetch) { int runLength; TextRun textRun; // fetch TextRun from the formatting state CharacterBufferRange charString = _settings.FetchTextRun( cpFetch, _cpFirst, out textRun, out runLength ); CultureInfo digitCulture = null; bool contextualSubstitution = false; bool symbolTypeface = false; Plsrun runType = TextRunInfo.GetRunType(textRun); if (runType == Plsrun.Text) { TextRunProperties properties = textRun.Properties; symbolTypeface = properties.Typeface.Symbol; if (!symbolTypeface) { _settings.DigitState.SetTextRunProperties(properties); digitCulture = _settings.DigitState.DigitCulture; contextualSubstitution = _settings.DigitState.Contextual; } } TextModifierScope currentScope = _modifierScope; TextModifier modifier = textRun as TextModifier; if (modifier != null) { _modifierScope = new TextModifierScope( _modifierScope, modifier, cpFetch ); // The new scope inclues the current TextModifier run currentScope = _modifierScope; } else if (_modifierScope != null && textRun is TextEndOfSegment) { // The new scope only affects subsequent runs. TextEndOfSegment run itself is // still in the old scope such that its coresponding TextModifier run can be tracked. _modifierScope = _modifierScope.ParentScope; } return new TextRunInfo( charString, runLength, cpFetch - _cpFirst, // offsetToFirstCp textRun, runType, 0, // charFlags digitCulture, contextualSubstitution, symbolTypeface, currentScope ); } ////// Split a TextRunInfo into multiple ranges each with a uniform set of /// TextEffects. /// ////// A TextRun can have a collection of TextEffect. Each of them can be applied to /// an arbitrary range of text. This method breaks the TextRunInfo into sub-ranges /// that have identical set of TextEffects. For example /// /// Current Run : |----------------------------------------| /// Effect 1: |-----------------------------------------------------| /// Effect 2: |------------------------| /// Splitted runs: |----------|------------------------|----| /// /// It can be observed that the effected ranges are dividied at the boundaries of the /// TextEffects. We sort all the boundaries of TextEffects according to their positions /// and create the effected range in between of any two ajacent boundaries. For each efffected /// range, we store all the active TextEffect into a list. /// private void SetTextEffectsVector( SpanVector textEffectsVector, int ich, TextRunInfo runInfo, TextEffectCollection textEffects ) { // We already check for empty text effects at the call site. Debug.Assert(textEffects != null && textEffects.Count != 0); int cpFetched = _cpFirst + _cchUpTo + ich; // get text source character index // Offset from client Cp to text effect index. int offset = cpFetched - _settings.TextSource.GetTextEffectCharacterIndexFromTextSourceCharacterIndex(cpFetched); int textEffectsCount = textEffects.Count; TextEffectBoundary[] bounds = new TextEffectBoundary[textEffectsCount * 2]; for (int i = 0; i < textEffectsCount; i++) { TextEffect effect = textEffects[i]; bounds[2 * i] = new TextEffectBoundary(effect.PositionStart, true); // effect starting boundary bounds[2 * i + 1] = new TextEffectBoundary(effect.PositionStart + effect.PositionCount, false); // effect end boundary } Array.Sort(bounds); // sort the TextEffect bounds. int effectedRangeStart = Math.Max(cpFetched - offset, bounds[0].Position); int effectedRangeEnd = Math.Min(cpFetched - offset + runInfo.Length, bounds[bounds.Length - 1].Position); int currentEffectsCount = 0; int currentPosition = effectedRangeStart; for (int i = 0; i < bounds.Length && currentPosition < effectedRangeEnd; i++) { // Have we reached the end of a non-empty subrange with at least one text effect? if (currentPosition < bounds[i].Position && currentEffectsCount > 0) { // Let [currentPosition,currentRangeEnd) delimit the subrange ending at bounds[i]. int currentRangeEnd = Math.Min(bounds[i].Position, effectedRangeEnd); // Consolidate all the active effects in the subrange. IListactiveEffects = new TextEffect[currentEffectsCount]; int effectIndex = 0; for (int j = 0; j < textEffectsCount; j++) { TextEffect effect = textEffects[j]; if (currentPosition >= effect.PositionStart && currentPosition < (effect.PositionStart + effect.PositionCount)) { activeEffects[effectIndex++] = effect; } } Invariant.Assert(effectIndex == currentEffectsCount); // Set the active effects for this CP subrange. The vector index is relative // to the starting cp of the current run-fetching loop. textEffectsVector.SetReference( currentPosition + offset - _cchUpTo - _cpFirst, // client cp index currentRangeEnd - currentPosition, // length activeEffects // text effects ); currentPosition = currentRangeEnd; } // Adjust the current count depending on if it is a TextEffect's starting or ending boundary. currentEffectsCount += (bounds[i].IsStart ? 1 : -1); if (currentEffectsCount == 0 && i < bounds.Length - 1) { // There is no effect on the current position. Move it to the start of next TextEffect. Invariant.Assert(bounds[i + 1].IsStart); currentPosition = Math.Max(currentPosition, bounds[i + 1].Position); } } } /// /// Structure representing one boundary of a TextEffect. Each TextEffect has /// two boundaries: the beginning and the end. /// private struct TextEffectBoundary : IComparable{ private readonly int _position; private readonly bool _isStart; internal TextEffectBoundary(int position, bool isStart) { _position = position; _isStart = isStart; } internal int Position { get { return _position; } } internal bool IsStart { get { return _isStart; } } public int CompareTo(TextEffectBoundary other) { if (Position != other.Position) return Position - other.Position; if (IsStart == other.IsStart) return 0; // Starting edge is always in front. return IsStart ? -1 : 1; } } /// /// Create special run that matches the content of specified text run /// ////// Critical: This code has unsafe code block that uses pointers. /// TreatAsSafe: This code does not expose the pointer and the call does bounds checking. /// [SecurityCritical,SecurityTreatAsSafe] private TextRunInfo CreateSpecialRunFromTextContent( TextRunInfo runInfo, int cchFetched ) { // -FORMAT ANCHOR- // // Format anchor character is what we create internally to drive LS. If it // is present in the middle of text stream sent from the client, we will // have to filter it out and replace it with NBSP. This is to protect LS // from running into a bad state due to misinterpreting such character as // our format anchor. Following is the list of anchor character we use todate. // // "\uFFFB" (Unicode 'Annotation Terminator') // // -LINEBREAK- // // Following the Unicode guideline on newline characters, we recognize // both LS (U+2028) and PS (U+2029) as explicit linebreak (PS also breaks // paragraph but that's handled outside line level formatting). We also // treat the following sequence of characters as linebreak // // "CR" ("\u000D") // "LF" ("\u000A") // "CRLF" ("\u000D\u000A") // "NEL" ("\u0085") // "VT" ("\u000B") // "FF" ("\u000C") // // Note: http://www.unicode.org/unicode/reports/tr13/tr13-9.html Debug.Assert(runInfo.StringLength > 0 && runInfo.Length > 0); CharacterBuffer charBuffer = runInfo.CharacterBuffer; int offsetToFirstChar = runInfo.OffsetToFirstChar; char firstChar = charBuffer[offsetToFirstChar]; ushort charFlags; charFlags = (ushort)Classification.CharAttributeOf( (int)Classification.GetUnicodeClassUTF16(firstChar) ).Flags; if ((charFlags & (ushort)CharacterAttributeFlags.CharacterLineBreak) != 0) { // Get cp length of newline sequence // // It is possible that client run ends in between two codepoints that // make up a single newline sequence e.g. CRLF. Therefore, when we // encounter the first codepoint of the sequence, we need to make sure // we have enough codepoints to determine the correct whole sequence. // In an uncommon event, we may be forced to look ahead by fetching more // runs. [wchao, PS bug 910308] int newlineLength = 1; // most sequences take one cp if (firstChar == '\r') { if (runInfo.Length > 1) { newlineLength += ((charBuffer[offsetToFirstChar + 1] == '\n') ? 1 : 0); } else { TextRunInfo nextRunInfo = FetchTextRun(_cpFirst + cchFetched + 1); if (nextRunInfo != null && nextRunInfo.TextRun is ITextSymbols) { newlineLength += ((nextRunInfo.CharacterBuffer[nextRunInfo.OffsetToFirstChar] == '\n') ? 1 : 0); } } } unsafe { runInfo = new TextRunInfo( new CharacterBufferRange((char*)PwchLineSeparator, 1), newlineLength, // run length runInfo.OffsetToFirstCp, runInfo.TextRun, Plsrun.LineBreak, // LineBreak run charFlags, null, // digit culture false, // contextual substitution false, // is not Unicode runInfo.TextModifierScope ); } } else if ((charFlags & (ushort)CharacterAttributeFlags.CharacterParaBreak) != 0) { unsafe { // This character is a paragraph separator. Split it into a // separate run. runInfo = new TextRunInfo( new CharacterBufferRange((char*)PwchParaSeparator, 1), 1, runInfo.OffsetToFirstCp, runInfo.TextRun, Plsrun.ParaBreak, // parabreak run charFlags, null, // digit culture false, // contextual substitution false, // is not Unicode runInfo.TextModifierScope ); } } else { Invariant.Assert((charFlags & (ushort)CharacterAttributeFlags.CharacterFormatAnchor) != 0); unsafe { runInfo = new TextRunInfo( new CharacterBufferRange((char*)PwchNbsp, 1), 1, // run length runInfo.OffsetToFirstCp, runInfo.TextRun, runInfo.Plsrun, charFlags, null, // digit culture false, // contextual substitution false, // is not Unicode runInfo.TextModifierScope ); } } return runInfo; } ////// Grab existing lsrun at specified LSCP /// private LSRun GrabLSRun( int lscpFetch, out Plsrun plsrun, out int lsrunOffset, out int lsrunLength ) { int offsetToFirstCp = lscpFetch - _cpFirst; SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, offsetToFirstCp); _plsrunVectorLatestPosition = rider.SpanPosition; plsrun = (Plsrun)rider.CurrentElement; LSRun lsrun; if (plsrun < Plsrun.FormatAnchor) { lsrun = ControlRuns[(int)plsrun]; lsrunOffset = 0; } else { lsrun = (LSRun)_lsrunList[(int)(ToIndex(plsrun) - Plsrun.FormatAnchor)]; lsrunOffset = offsetToFirstCp - rider.CurrentSpanStart; } if (_lscpFirstValue != 0) { // this is marker store, differentiate the plsrun from // plsrun from the main text store. plsrun = MakePlsrunMarker(plsrun); } // SpanRider.Length yields the distance to the end of the Span, not // the total length of the Span. lsrunLength = rider.Length; return lsrun; } ////// Get the Bidi level of the character before the currently fetched one /// private int GetLastLevel() { if (_lscchUpTo > 0) { SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, _lscchUpTo - 1); _plsrunVectorLatestPosition = rider.SpanPosition; return GetRun((Plsrun)rider.CurrentElement).BidiLevel; } return BaseBidiLevel; } ////// Base bidi level /// private int BaseBidiLevel { get { return _settings.Pap.RightToLeft ? 1 : 0; } } ////// Analyze bidirectional level of runs /// /// run info vector indexed by ich /// character length of string to be analyzed /// array of bidi levels, each for a character ///Number of characters resolved ////// BiDi Analysis in line layout imposes a higher level protocol on top of Unicode bidi algorithm /// to support rich editing behavior. Explicit directional embedding controls is to be done /// through TextModifier runs and corresponding TextEndOfSegment. Directional controls (such as /// LRE, RLE, PDF, etc) in the text stream are ignored in the Bidi Analysis to avoid conflict with the higher /// level protocol. /// /// The implementation analyzes directional embedding one level at a time. Input text runs are divided /// at the point where directional embedding level is changed. /// private int BidiAnalyze( SpanVector runInfoVector, int stringLength, out byte[] bidiLevels ) { CharacterBuffer charBuffer = null; int offsetToFirstChar; SpanRider runInfoSpanRider = new SpanRider(runInfoVector); if (runInfoSpanRider.Length >= stringLength) { // typical case, only one string is analyzed TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement; if (!runInfo.IsSymbol) { charBuffer = runInfo.CharacterBuffer; offsetToFirstChar = runInfo.OffsetToFirstChar; Debug.Assert(runInfo.StringLength >= stringLength); } else { // Treat all characters in non-Unicode runs as strong left-to-right. // The literal 'A' could be any Latin character. charBuffer = new StringCharacterBuffer(new string('A', stringLength)); offsetToFirstChar = 0; } } else { // build up a consolidated character buffer for bidi analysis of // concatenated strings in multiple textruns. int ich = 0; int cch; StringBuilder stringBuilder = new StringBuilder(stringLength); while(ich < stringLength) { runInfoSpanRider.At(ich); cch = runInfoSpanRider.Length; TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement; Debug.Assert(cch <= runInfo.StringLength); if (!runInfo.IsSymbol) { runInfo.CharacterBuffer.AppendToStringBuilder( stringBuilder, runInfo.OffsetToFirstChar, cch ); } else { // Treat all characters in non-Unicode runs as strong left-to-right. // The literal 'A' could be any Latin character. stringBuilder.Append('A', cch); } ich += cch; } charBuffer = new StringCharacterBuffer(stringBuilder.ToString()); offsetToFirstChar = 0; } if(_bidiState == null) { // make sure the initial state is setup _bidiState = new BidiState(_settings, _cpFirst); } bidiLevels = new byte[stringLength]; DirectionClass[] directionClasses = new DirectionClass[stringLength]; int resolvedLength = 0; for(int i = 0; i < runInfoVector.Count; i++) { int cchResolved = 0; TextRunInfo currentRunInfo = (TextRunInfo) runInfoVector[i].element; TextModifier modifier = currentRunInfo.TextRun as TextModifier; if (IsDirectionalModifier(modifier)) { bidiLevels[resolvedLength] = AnalyzeDirectionalModifier(_bidiState, modifier.FlowDirection); cchResolved = 1; } else if (IsEndOfDirectionalModifier(currentRunInfo)) { bidiLevels[resolvedLength] = AnalyzeEndOfDirectionalModifier(_bidiState); cchResolved = 1; } else { int ich = resolvedLength; do { CultureInfo culture = CultureMapper.GetSpecificCulture(currentRunInfo.Properties == null ? null : currentRunInfo.Properties.CultureInfo); DirectionClass europeanNumberOverride = _bidiState.GetEuropeanNumberClassOverride(culture); // // The European number in the input text is explictly set to AN or EN base on the // culture of the text. We set the input DirectionClass of this range of text to // AN or EN to indicate that any EN in this range should be explicitly set to this override // value. // for(int k = 0; k < runInfoVector[i].length; k++) { directionClasses[ich + k] = europeanNumberOverride; } ich += runInfoVector[i].length; if ((++i) >= runInfoVector.Count) break; // end of all runs. currentRunInfo = (TextRunInfo) runInfoVector[i].element; if ( currentRunInfo.Plsrun == Plsrun.Hidden && ( IsDirectionalModifier(currentRunInfo.TextRun as TextModifier) || IsEndOfDirectionalModifier(currentRunInfo) ) ) { i--; break; // break bidi analysis at the point of embedding level change } } while (true); const Bidi.Flags BidiFlags = Bidi.Flags.ContinueAnalysis | Bidi.Flags.IgnoreDirectionalControls | Bidi.Flags.OverrideEuropeanNumberResolution; // The last runs will be marked as IncompleteText as their resolution // may depend on following runs that haven't been fetched yet. Bidi.Flags flags = (i < runInfoVector.Count) ? BidiFlags : BidiFlags | Bidi.Flags.IncompleteText; Bidi.BidiAnalyzeInternal( charBuffer, offsetToFirstChar + resolvedLength, ich - resolvedLength, 0, // no max hint flags, _bidiState, new PartialArray(bidiLevels, resolvedLength, ich - resolvedLength), new PartialArray (directionClasses, resolvedLength, ich - resolvedLength), out cchResolved ); // Text must be completely resolved if there is no IncompleteText flag. Invariant.Assert(cchResolved == ich - resolvedLength || (flags & Bidi.Flags.IncompleteText) != 0); } resolvedLength += cchResolved; } Invariant.Assert(resolvedLength <= bidiLevels.Length); return resolvedLength; } /// /// Update BidiState base to the new directional embedding level. /// ////// The method returns the embedding level before the start of the Modifier. /// Contents inside the modifier scope is at a higher embedding level and hence /// separated from the content before the modifier scope. /// private byte AnalyzeDirectionalModifier( BidiState state, FlowDirection flowDirection ) { bool leftToRight = (flowDirection == FlowDirection.LeftToRight); ulong levelStack = state.LevelStack; byte parentLevel = Bidi.BidiStack.GetMaximumLevel(levelStack); byte topLevel; // Push to Bidi stack. Increment overflow counter if so. if (!Bidi.BidiStack.Push(ref levelStack, leftToRight, out topLevel)) { state.Overflow++; } state.LevelStack = levelStack; // set the default last strong such that text without CultureInfo // can be resolved correctly. state.SetLastDirectionClassesAtLevelChange(); return parentLevel; } ////// Update BidiState at the end of a directional emebedding level. /// ////// The method returns the embedding level after the end of the modifier. /// Contents inside the modifier scope is at a higher embedding level and hence separated /// from the content after the modifier scope. /// private byte AnalyzeEndOfDirectionalModifier(BidiState state) { // Pop level stack if (state.Overflow > 0) { state.Overflow --; return state.CurrentLevel; } byte parentLevel; ulong stack = state.LevelStack; bool success = Bidi.BidiStack.Pop(ref stack, out parentLevel); Invariant.Assert(success); state.LevelStack = stack; // set the default last strong such that text without CultureInfo // can be resolved correctly. state.SetLastDirectionClassesAtLevelChange(); return parentLevel; } private bool IsEndOfDirectionalModifier(TextRunInfo runInfo) { return ( runInfo.TextModifierScope != null && runInfo.TextModifierScope.TextModifier.HasDirectionalEmbedding && runInfo.TextRun is TextEndOfSegment ); } private bool IsDirectionalModifier(TextModifier modifier) { return modifier != null && modifier.HasDirectionalEmbedding; } internal bool InsertFakeLineBreak(int cpLimit) { for (int i = 0, cp = 0, lscp = 0; i < _plsrunVector.Count; ++i) { Span span = _plsrunVector[i]; Plsrun plsrun = (Plsrun)span.element; // Is it a normal, non-static, LSRun? if (plsrun >= Plsrun.FormatAnchor) { // Get the run. LSRun lsrun = GetRun(plsrun); // Have we reached the limit? if (cp + lsrun.Length >= cpLimit) { // Remove all subsequent runs. _plsrunVector.Delete(i + 1, _plsrunVector.Count - (i + 1), ref _plsrunVectorLatestPosition); // Truncate the run if it exeeds the limit. if (lsrun.Type == Plsrun.Text && cp + lsrun.Length > cpLimit) { lsrun.Truncate(cpLimit - cp); span.length = lsrun.Length; } _lscchUpTo = lscp + lsrun.Length; // Close any reverse runs. CreateReverseLSRuns(BaseBidiLevel, lsrun.BidiLevel); // Add the fake line break. _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, Plsrun.FakeLineBreak, _plsrunVectorLatestPosition); _lscchUpTo += 1; return true; } cp += lsrun.Length; } lscp += span.length; } return false; } ////// Determines whether a line needs to be truncated for security reasons due to exceeding /// the maximum number of characters per line. See the comment for MaxCharactersPerLine. /// /// Vector of fetched text runs. /// Number of cp to be added to _plsrunVector; the method /// may change this value if the line needs to be truncated. ///Returns true if the line should be truncated, false it not. private bool IsForceBreakRequired(SpanVector runInfoVector, ref int cchToAdd) { bool forceBreak = false; int ichRun = 0; for (int i = 0; i < runInfoVector.Count && ichRun < cchToAdd; ++i) { Span span = runInfoVector[i]; TextRunInfo runInfo = (TextRunInfo)span.element; int runLength = Math.Min(span.length, cchToAdd - ichRun); // Only Plsrun.Text runs count against the limit if (runInfo.Plsrun == Plsrun.Text && !IsNewline((ushort)runInfo.CharacterAttributeFlags)) { if (_accTextLengthSoFar + runLength <= MaxCharactersPerLine) { // we're still under the limit; accumulate the number of characters so far _accTextLengthSoFar += runLength; } else { // accumulated number of characters has exceeded the maximum allowed number // of characters per line; we need to generate a fake line break runLength = MaxCharactersPerLine - _accTextLengthSoFar; _accTextLengthSoFar = MaxCharactersPerLine; cchToAdd = ichRun + runLength; forceBreak = true; } } ichRun += runLength; } return forceBreak; } [Flags] private enum NumberContext { Unknown = 0, Arabic = 0x0001, European = 0x0002, Mask = 0x0003, FromLetter = 0x0004, FromFlowDirection = 0x0008 } private NumberContext GetNumberContext(TextModifierScope scope) { int limitCp = _cpFirst + _cchUpTo; int firstCp = _cpNumberContext; NumberContext cachedNumberContext = _numberContext; // Is there a current bidi scope? for (; scope != null; scope = scope.ParentScope) { if (scope.TextModifier.HasDirectionalEmbedding) { int cpScope = scope.TextSourceCharacterIndex; if (cpScope >= _cpNumberContext) { // Only scan back to the start of the current scope and don't use the cached number // context since it's outside the current scope. firstCp = cpScope; cachedNumberContext = NumberContext.Unknown; } break; } } // Is it a right to left context? bool rightToLeft = (scope != null) ? scope.TextModifier.FlowDirection == FlowDirection.RightToLeft : Pap.RightToLeft; // Scan for a preceding letter. while (limitCp > firstCp) { TextSpantextSpan = _settings.GetPrecedingText(limitCp); // Stop if there's an empty TextSpan if (textSpan.Length <= 0) { break; } CharacterBufferRange charRange = textSpan.Value.CharacterBufferRange; if (!charRange.IsEmpty) { CharacterBuffer charBuffer = charRange.CharacterBuffer; // Index just past the last character in the range. int limit = charRange.OffsetToFirstChar + charRange.Length; // Index of the first character in the range, not including any characters before firstCp. int first = limit - Math.Min(charRange.Length, limitCp - firstCp); // We'll stop scanning at letter or line break. const ushort flagsMask = (ushort)CharacterAttributeFlags.CharacterLetter | (ushort)CharacterAttributeFlags.CharacterLineBreak; // Iterate over the characters in reverse order. for (int i = limit - 1; i >= first; --i) { char ch = charBuffer[i]; CharacterAttribute charAttributes = Classification.CharAttributeOf(Classification.GetUnicodeClassUTF16(ch)); ushort flags = (ushort)(charAttributes.Flags & flagsMask); if (flags != 0) { if ((flags & (ushort)CharacterAttributeFlags.CharacterLetter) != 0) { // It's a letter so the number context depends on its script. return (charAttributes.Script == (byte)ScriptID.Arabic || charAttributes.Script == (byte)ScriptID.Syriac) ? NumberContext.Arabic | NumberContext.FromLetter : NumberContext.European | NumberContext.FromLetter; } else { // It's a line break. There are no preceding letters so number context depends only on // whether the current bidi scope is right to left. return rightToLeft ? NumberContext.Arabic | NumberContext.FromFlowDirection : NumberContext.European | NumberContext.FromFlowDirection; } } } } limitCp -= textSpan.Length; } // If we have a cached number context that's still valid the use it. Valid means (1) we // scanned back as far as the cp of the number context, and (2) the number context was // determined from a letter. (A cached number context derived from flow direction might // not be valid because an embedded bidi level may have ended.) if (limitCp <= firstCp && (cachedNumberContext & NumberContext.FromLetter) != 0) { return cachedNumberContext; } // There are no preceding letters so number context depends only on whether the current // bidi scope is right to left. return rightToLeft ? NumberContext.Arabic | NumberContext.FromFlowDirection : NumberContext.European | NumberContext.FromFlowDirection; } /// /// Create lsruns within a range of uniform bidi level. /// private void CreateLSRunsUniformBidiLevel( SpanVector runInfoVector, SpanVector textEffectsVector, int runInfoFirstCp, int ichUniform, int cchUniform, int uniformBidiLevel, TextFormattingMode textFormattingMode, bool isSideways, ref int lastBidiLevel ) { int ichRun = 0; // a range of characters with uniform level may span multiple // textruns. Create lsrun at runInfo boundary. SpanRider runInfoSpanRider = new SpanRider(runInfoVector); SpanRider textEffectsSpanRider = new SpanRider(textEffectsVector); while(ichRun < cchUniform) { runInfoSpanRider.At(ichUniform + ichRun); textEffectsSpanRider.At(ichUniform + ichRun); // Limit the span base on effected ranges. int spanLength = Math.Min(runInfoSpanRider.Length, textEffectsSpanRider.Length); int ichEnd = Math.Min(ichRun + spanLength, cchUniform); int textRunLength; TextRunInfo runInfo = (TextRunInfo)runInfoSpanRider.CurrentElement; IListtextEffects = (IList )textEffectsSpanRider.CurrentElement; // Initialize digitCulture only if there are digits. CultureInfo digitCulture = null; // Number context; used only if we do contextual digit substitution. NumberContext numberContext = NumberContext.Unknown; if ((runInfo.CharacterAttributeFlags & (ushort)CharacterAttributeFlags.CharacterDigit) == 0) { // No digits so digitCulture isn't used. } else if (!runInfo.ContextualSubstitution) { // Render all numbers using the digit culture of the run. digitCulture = runInfo.DigitCulture; } else { // Contextual number substitution means the digit culture of a given number depends on the // nearest preceding letter. If it's an Arabic letter we use the digit culture of the run; // otherwise we use European digits (null digit culture). // Number context of the previous number, if any. NumberContext previousNumberContext = NumberContext.Unknown; CharacterBuffer charBuffer = runInfo.CharacterBuffer; // The cha----r indexes ichRun, ich, etc., are relative to the start of the uniform range; // In order to yield an index into charBuffer, we need to calculate the offset from the // start of the uniform range to the start of the character buffer. // // // _cpFirst // |----_cchUpTo---->|-----------------runInfoVector----------------------->| // | // |--ichUniform-->|----ich------>| // | | | // |-----CurrentSpanStart-->|=====x=====runInfo========| // | | | // charBuffer=> [---runInfo.OffsetToFirstChar--->|-----x----------------------------] // | | | // |---characterOffset---->|----ich------>| // int characterOffset = ichUniform // start of the uniform range - runInfoSpanRider.CurrentSpanStart // make relative to the the start of the runInfo + runInfo.OffsetToFirstChar; // make relative to the start of the character buffer in runInfo for (int ich = ichRun; ich < ichEnd; ++ich) { char ch = charBuffer[ich + characterOffset]; CharacterAttribute charAttributes = Classification.CharAttributeOf(Classification.GetUnicodeClassUTF16(ch)); if ((charAttributes.Flags & (ushort)CharacterAttributeFlags.CharacterDigit) != 0) { // If there were no preceding letters in the current run we need to scan backwards to // determine the current number context. if (numberContext == NumberContext.Unknown) { numberContext = GetNumberContext(runInfo.TextModifierScope); } // We need to set the digit culture if // (a) we haven't set it yet (i.e., this is the first number) or // (b) we set it but the previous number had a different number context if ((previousNumberContext & NumberContext.Mask) != (numberContext & NumberContext.Mask)) { // If there was a previous number with a different digit culture we need to split the run. if (previousNumberContext != NumberContext.Unknown) { CreateLSRuns( runInfo, textEffects, digitCulture, ichUniform + ichRun - runInfoSpanRider.CurrentSpanStart, ich - ichRun, uniformBidiLevel, textFormattingMode, isSideways, ref lastBidiLevel, out textRunLength ); _cchUpTo += textRunLength; ichRun = ich; } // Set the digitCulture to use for this and subsequent characters. digitCulture = (numberContext & NumberContext.Mask) == NumberContext.Arabic ? runInfo.DigitCulture : // subsequent digits use Arabic symbols null; // subsequent digits use European symbols previousNumberContext = numberContext; } } else if ((charAttributes.Flags & (ushort)CharacterAttributeFlags.CharacterLetter) != 0) { // It's a letter so set the current number context based on the letter's script. // Don't set the digit culture until we actually encounter a number. numberContext = (charAttributes.Script == (byte)ScriptID.Arabic || charAttributes.Script == (byte)ScriptID.Syriac) ? NumberContext.Arabic | NumberContext.FromLetter : NumberContext.European | NumberContext.FromLetter; } } } // Even if we split the run we still have to add the last part. Debug.Assert(ichRun < ichEnd); // Add the run (or what's left of it). CreateLSRuns( runInfo, textEffects, digitCulture, ichUniform + ichRun - runInfoSpanRider.CurrentSpanStart, ichEnd - ichRun, uniformBidiLevel, textFormattingMode, isSideways, ref lastBidiLevel, out textRunLength ); _cchUpTo += textRunLength; ichRun = ichEnd; // Save the number of context if known. This reduces the number of calls to GetPrecedingText for // lines with more than one number. We do this now, after calling CreateLSRuns, so that _cchUpTo // holds the correct cp that corresponds to all the characters scanned so far. if (numberContext != NumberContext.Unknown) { _numberContext = numberContext; _cpNumberContext = _cpFirst + _cchUpTo; } } Debug.Assert(ichRun == cchUniform); } /// /// Create reverse lsruns /// /// current bidi level /// last bidi level ///updated last bidi Level private int CreateReverseLSRuns( int currentBidiLevel, int lastBidiLevel ) { Plsrun plsrun; int levelDiff = currentBidiLevel - lastBidiLevel; if(levelDiff > 0) { // level up plsrun = Plsrun.Reverse; } else { // level down plsrun = Plsrun.CloseAnchor; levelDiff = -levelDiff; } for(int i = 0; i < levelDiff; i++) { _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, 1, plsrun, _plsrunVectorLatestPosition); _lscchUpTo++; } return currentBidiLevel; } ////// Create lsrun(s) /// /// run info /// The applicable TextEffects on the LSRun. /// digit culture for number substitution /// offset the first char from start of run info /// lsrun character length /// uniform bidi level /// The layout mode /// Whether the text in the run should be sideways /// last bidi level /// text run length private void CreateLSRuns( TextRunInfo runInfo, IListtextEffects, CultureInfo digitCulture, int offsetToFirstChar, int stringLength, int uniformBidiLevel, TextFormattingMode textFormattingMode, bool isSideways, ref int lastBidiLevel, out int textRunLength ) { LSRun lsrun = null; int lsrunLength = 0; textRunLength = 0; switch (runInfo.Plsrun) { case Plsrun.Text: { ushort charFlags = (ushort)runInfo.CharacterAttributeFlags; // LineBreak & ParaBreak are separated into individual TextRunInfo with Plsrun.LineBreak or Plsrun.ParaBreak. Invariant.Assert(!IsNewline(charFlags)); if ((charFlags & (ushort)CharacterAttributeFlags.CharacterFormatAnchor) != 0) { lsrun = new LSRun( runInfo, Plsrun.FormatAnchor, PwchNbsp, 1, runInfo.OffsetToFirstCp, (byte) uniformBidiLevel ); lsrunLength = textRunLength = lsrun.StringLength; } else { // Normal text, run length is character length textRunLength = lsrunLength = stringLength; Debug.Assert(runInfo.OffsetToFirstChar + offsetToFirstChar + lsrunLength <= runInfo.CharacterBuffer.Count); CreateTextLSRuns( runInfo, textEffects, digitCulture, offsetToFirstChar, stringLength, uniformBidiLevel, textFormattingMode, isSideways, ref lastBidiLevel ); } break; } case Plsrun.InlineObject: { Debug.Assert(offsetToFirstChar == 0); double realToIdeal = TextFormatterImp.ToIdeal; lsrun = new LSRun( runInfo, textEffects, Plsrun.InlineObject, runInfo.OffsetToFirstCp, runInfo.Length, (int)Math.Round(realToIdeal * runInfo.TextRun.Properties.FontRenderingEmSize), 0, // character flags new CharacterBufferRange(runInfo.CharacterBuffer, 0, stringLength), null, // no shapeable realToIdeal, (byte)uniformBidiLevel ); lsrunLength = stringLength; textRunLength = runInfo.Length; break; } case Plsrun.LineBreak: { // // Line Separator's BIDI class is Neutral (WS). It would take the class of surrounding // characters so it might end up in a reverse run. However, LS would not process Line Separator // in reverse run. Here we always override the BIDI level of Line Separator to paragraph's // embedding level such that it is out of reverse run and LS would process it correctly. // uniformBidiLevel = (Pap.RightToLeft ? 1 : 0); lsrun = CreateLineBreakLSRun( runInfo, stringLength, out lsrunLength, out textRunLength ); break; } case Plsrun.ParaBreak: { // // Paragraph Separator ends the paragraph. Its bidi level must be the embedding level. // Debug.Assert(uniformBidiLevel == (Pap.RightToLeft ? 1 : 0)); lsrun = CreateLineBreakLSRun( runInfo, stringLength, out lsrunLength, out textRunLength ); break; } case Plsrun.Hidden: { // hidden run yields the same cp as its lscp lsrunLength = runInfo.Length - offsetToFirstChar; textRunLength = lsrunLength; lsrun = new LSRun( runInfo, Plsrun.Hidden, PwchHidden, textRunLength, runInfo.OffsetToFirstCp, (byte) uniformBidiLevel ); break; } } if(lsrun != null) { Debug.Assert(lsrunLength > 0); if (lastBidiLevel != uniformBidiLevel) { lastBidiLevel = CreateReverseLSRuns(uniformBidiLevel, lastBidiLevel); } // Add the plsrun to the span vector. _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, lsrunLength, AddLSRun(lsrun), _plsrunVectorLatestPosition); _lscchUpTo += lsrunLength; } } /// /// Break down text with uniform level into multiple shapeable runs, /// then create one LSRun for each of them. /// private void CreateTextLSRuns( TextRunInfo runInfo, IListtextEffects, CultureInfo digitCulture, int offsetToFirstChar, int stringLength, int uniformBidiLevel, TextFormattingMode textFormattingMode, bool isSideways, ref int lastBidiLevel ) { ICollection shapeables = null; ITextSymbols textSymbols = runInfo.TextRun as ITextSymbols; if (textSymbols != null) { bool isRightToLeftParagraph = (runInfo.TextModifierScope != null) ? runInfo.TextModifierScope.TextModifier.FlowDirection == FlowDirection.RightToLeft : _settings.Pap.RightToLeft; shapeables = textSymbols.GetTextShapeableSymbols( _settings.Formatter.GlyphingCache, new CharacterBufferReference( runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar ), stringLength, (uniformBidiLevel & 1) != 0, // Indicates the RTL based on the bidi level of text. isRightToLeftParagraph, digitCulture, runInfo.TextModifierScope, textFormattingMode, isSideways ); } else { TextShapeableSymbols textShapeableSymbols = runInfo.TextRun as TextShapeableSymbols; if (textShapeableSymbols != null) { shapeables = new TextShapeableSymbols[] { textShapeableSymbols }; } } if (shapeables == null) { Debug.Assert(false); throw new NotSupportedException(); } double realToIdeal = TextFormatterImp.ToIdeal; int ich = 0; foreach (TextShapeableSymbols shapeable in shapeables) { int cch = shapeable.Length; Debug.Assert(cch > 0 && cch <= stringLength - ich); int currentBidiLevel = uniformBidiLevel; LSRun lsrun = new LSRun( runInfo, textEffects, Plsrun.Text, runInfo.OffsetToFirstCp + offsetToFirstChar + ich, cch, (int)Math.Round(realToIdeal * runInfo.TextRun.Properties.FontRenderingEmSize), runInfo.CharacterAttributeFlags, new CharacterBufferRange(runInfo.CharacterBuffer, runInfo.OffsetToFirstChar + offsetToFirstChar + ich, cch), shapeable, realToIdeal, (byte)currentBidiLevel ); if (currentBidiLevel != lastBidiLevel) { lastBidiLevel = CreateReverseLSRuns(currentBidiLevel, lastBidiLevel); } // set up an LSRun for each shapeable. _plsrunVectorLatestPosition = _plsrunVector.SetValue(_lscchUpTo, cch, AddLSRun(lsrun), _plsrunVectorLatestPosition); _lscchUpTo += cch; ich += cch; } } /// /// Create LSRun for a linebreak run /// private LSRun CreateLineBreakLSRun( TextRunInfo runInfo, int stringLength, out int lsrunLength, out int textRunLength ) { lsrunLength = stringLength; textRunLength = runInfo.Length; return new LSRun( runInfo, null, // No TextEffects on LineBreak runInfo.Plsrun, runInfo.OffsetToFirstCp, runInfo.Length, 0, // emSize runInfo.CharacterAttributeFlags, new CharacterBufferRange(runInfo.CharacterBuffer, runInfo.OffsetToFirstChar, stringLength), null, // no shapebale TextFormatterImp.ToIdeal, (byte)(Pap.RightToLeft ? 1 : 0) ); } ////// Add new lsrun to lsrun list /// /// lsrun to add ///plsrun of added lsrun private Plsrun AddLSRun(LSRun lsrun) { if(lsrun.Type < Plsrun.FormatAnchor) { return lsrun.Type; } Plsrun plsrun = (Plsrun)((uint)_lsrunList.Count + Plsrun.FormatAnchor); if (lsrun.IsSymbol) { plsrun = MakePlsrunSymbol(plsrun); } _lsrunList.Add(lsrun); return plsrun; } #region lsrun/cp mapping ////// Map internal LSCP to text source cp /// ////// This method does not handle mapping of LSCP beyond the last one /// internal int GetExternalCp(int lscp) { lscp -= _lscpFirstValue; SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition, lscp - _cpFirst); _plsrunVectorLatestPosition = rider.SpanPosition; return GetRun((Plsrun)rider.CurrentElement).OffsetToFirstCp + lscp - rider.CurrentSpanStart; } ////// Get LSRun from plsrun /// internal LSRun GetRun(Plsrun plsrun) { plsrun = ToIndex(plsrun); return (LSRun)( IsContent(plsrun) ? _lsrunList[(int)(plsrun - Plsrun.FormatAnchor)] : ControlRuns[(int)plsrun] ); } ////// Check if plsrun is marker /// internal static bool IsMarker(Plsrun plsrun) { return (plsrun & Plsrun.IsMarker) != 0; } ////// Make this plsrun a marker plsrun /// internal static Plsrun MakePlsrunMarker(Plsrun plsrun) { return (plsrun | Plsrun.IsMarker); } ////// Make this plsrun a symbol plsrun /// internal static Plsrun MakePlsrunSymbol(Plsrun plsrun) { return (plsrun | Plsrun.IsSymbol); } ////// Convert plsrun to index to lsrun list /// internal static Plsrun ToIndex(Plsrun plsrun) { return (plsrun & Plsrun.UnmaskAll); } ////// Check if run is content /// internal static bool IsContent(Plsrun plsrun) { plsrun = ToIndex(plsrun); return plsrun >= Plsrun.FormatAnchor; } ////// Check if character is space /// internal static bool IsSpace(char ch) { return ch == ' ' || ch == '\u00a0'; } ////// Check if character is of strong directional type /// internal static bool IsStrong(char ch) { int unicodeClass = Classification.GetUnicodeClass(ch); ItemClass itemClass = (ItemClass)Classification.CharAttributeOf(unicodeClass).ItemClass; return itemClass == ItemClass.StrongClass; } ////// Check if the run is a line break or paragraph break /// internal static bool IsNewline (Plsrun plsrun) { return plsrun == Plsrun.LineBreak || plsrun == Plsrun.ParaBreak; } ////// Check if the character is a line break or paragraph break character /// internal static bool IsNewline(ushort flags) { return ( (flags & (ushort) CharacterAttributeFlags.CharacterLineBreak) != 0 || (flags & (ushort) CharacterAttributeFlags.CharacterParaBreak) != 0 ); } #endregion ////// Repositioning text lsruns according to its BaselineAlignmentment property /// internal void AdjustRunsVerticalOffset( int dcpLimit, int height, int baselineOffset, out int cellHeight, out int cellAscent ) { // Following are all alignment point offsets from the baseline of the line. // Value grows positively in paragraph flow direction. int top = 0; int bottom = 0; int textTop = 0; int textBottom = 0; int super = 0; int sub = 0; int center = 0; ArrayList lsruns = new ArrayList(3); // Find TextTop from all Baseline lsruns int dcp = 0; int i = 0; while(dcp < dcpLimit) { Debug.Assert(i < _plsrunVector.Count); Span span = _plsrunVector[i++]; LSRun lsrun = GetRun((Plsrun)span.element); if( lsrun.Type == Plsrun.Text || lsrun.Type == Plsrun.InlineObject) { if(lsrun.RunProp.BaselineAlignment == BaselineAlignment.Baseline) { textTop = Math.Max(textTop, lsrun.BaselineOffset); textBottom = Math.Max(textBottom, lsrun.Descent); } lsruns.Add(lsrun); } dcp += span.length; } textTop = -textTop; // offset from the baseline in paragraph flow direction top = height > 0 ? -baselineOffset : textTop; // Finalize Bottom by ignoring all but Top, TextTop and Baseline lsruns foreach(LSRun lsrun in lsruns) { switch (lsrun.RunProp.BaselineAlignment) { case BaselineAlignment.Top: textBottom = Math.Max(textBottom, lsrun.Height + top); break; case BaselineAlignment.TextTop: textBottom = Math.Max(textBottom, lsrun.Height + textTop); break; } } bottom = height > 0 ? height - baselineOffset : textBottom; // hardcode the positions for now center = (top + bottom) / 2; sub = bottom / 2; super = top * 2 / 3; // Now move all lsruns according to its BaselineAlignment property cellAscent = 0; int cellDescent = 0; foreach(LSRun lsrun in lsruns) { int move = 0; switch (lsrun.RunProp.BaselineAlignment) { case BaselineAlignment.Top: // lsrun top to line top move = top + lsrun.BaselineOffset; break; case BaselineAlignment.Bottom: // lsrun bottom to line bottom move = bottom - lsrun.Height + lsrun.BaselineOffset; break; case BaselineAlignment.TextTop: // lsrun top to line text top move = textTop + lsrun.BaselineOffset; break; case BaselineAlignment.TextBottom: // lsrun bottom to line text bottom move = textBottom - lsrun.Height + lsrun.BaselineOffset; break; case BaselineAlignment.Center: // lsrun center to line center move = center - lsrun.Height/2 + lsrun.BaselineOffset; break; case BaselineAlignment.Superscript: // lsrun baseline to line superscript move = super; break; case BaselineAlignment.Subscript: // lsrun baseline to line subscript move = sub; break; } lsrun.Move(move); // Recalculate line ascent and descent cellAscent = Math.Max(cellAscent, lsrun.BaselineOffset - move); cellDescent = Math.Max(cellDescent, lsrun.Descent + move); } cellHeight = cellAscent + cellDescent; } ////// Collect a piece of raw text that makes up a word containing the specified LSCP. /// The text returned from this method is used for hyphenation of a single word. /// In addition to the raw text, it also returns the mapping between the raw character /// indices and the LSCP indices in plsrunVector. This is used later on when we /// map the lexical result back to the positions used to communicate with LS. /// ////// "word" here is not meant for a linguistic term. It only means array of characters /// from space to space i.e. a word in SE Asian language is not separated by spaces. /// internal char[] CollectRawWord( int lscpCurrent, bool isCurrentAtWordStart, bool isSideways, out int lscpChunk, out int lscchChunk, out CultureInfo textCulture, out int cchWordMax, out SpanVectortextVector ) { // Fetch the lsrun containing the current position make sure // all LSCP before it are properly retained in plsrun vector. // We need this before we could reliably walk back the plsrun // vector to establish the chunk's start position in the following // step. textVector = new SpanVector (); textCulture = null; lscpChunk = lscpCurrent; lscchChunk = 0; cchWordMax = 0; Plsrun plsrun; int lsrunOffset; int lsrunLength; LSRun lsrun = FetchLSRun( lscpCurrent, Settings.Formatter.TextFormattingMode, isSideways, out plsrun, out lsrunOffset, out lsrunLength ); if (lsrun == null) return null; textCulture = lsrun.TextCulture; int lscpLim; int cchBefore = 0; if (!isCurrentAtWordStart && lscpChunk > _cpFirst) { // The specified position may not be start of word. // Expand backward to the first non-space character following a space // before the current position. If the current position is already // at space, no skip is needed. SpanRider rider = new SpanRider(_plsrunVector, _plsrunVectorLatestPosition); do { rider.At(lscpChunk - _cpFirst - 1); lscpLim = rider.CurrentSpanStart + _cpFirst; lsrun = GetRun((Plsrun)rider.CurrentElement); if ( IsNewline(lsrun.Type) || lsrun.Type == Plsrun.InlineObject) { // Stop expanding due to hard break break; } if (lsrun.Type == Plsrun.Text) { if (!lsrun.TextCulture.Equals(textCulture)) { // Stop expanding due to change of text culture break; } int cchLim = lscpChunk - lscpLim; int ichFirst = lsrun.OffsetToFirstChar + lscpChunk - _cpFirst - rider.CurrentSpanStart; int cch = 0; // Skip all non-space characters until a space is found while (cch < cchLim && !IsSpace(lsrun.CharacterBuffer[ichFirst - cch - 1])) cch++; cchBefore += cch; if (cch < cchLim) { // Start of chunk is found lscpChunk -= cch; break; } } // Reposition start of chunk to the beginning of the current run and continue expanding Invariant.Assert(lscpLim < lscpChunk); lscpChunk = lscpLim; } while (lscpChunk > _cpFirst && cchBefore <= MaxCchWordToHyphenate); _plsrunVectorLatestPosition = rider.SpanPosition; } if (cchBefore > MaxCchWordToHyphenate) { // The word is unusually long. This is already a situation we dont want // bring hyphenation into the picture. return null; } // Expand forward from the beginning of a word to the end of the word. // If the start position is already at space, skip passed all leading spaces StringBuilder stringBuilder = null; int lscp = lscpChunk; int cchText = 0; int cchLastWord = 0; do { lsrun = FetchLSRun( lscp, Settings.Formatter.TextFormattingMode, isSideways, out plsrun, out lsrunOffset, out lsrunLength ); if (lsrun == null) return null; lscpLim = lscp + lsrunLength; if ( IsNewline(lsrun.Type) || lsrun.Type == Plsrun.InlineObject) { // Stop expanding due to hard break break; } if (lsrun.Type == Plsrun.Text) { if (!lsrun.TextCulture.Equals(textCulture)) { // Stop expanding due to change of text culture break; } int cchLim = lscpLim - lscp; int ichFirst = lsrun.OffsetToFirstChar + lsrun.Length - lsrunLength; int cch = 0; if (cchText == 0) { // Skip all leading spaces while (cch < cchLim && IsSpace(lsrun.CharacterBuffer[ichFirst + cch])) cch++; } // Skip all non-space characters until a following space is found char ch; int cchWord = cchLastWord; while ( cch < cchLim && cchText + cch < MaxCchWordToHyphenate && !IsSpace((ch = lsrun.CharacterBuffer[ichFirst + cch]))) { cch++; if (IsStrong(ch)) { cchWord++; } else { // Non-strong character marks the end of the current word length, // Keep the length of the greatest length word found so far. if (cchWord > cchWordMax) cchWordMax = cchWord; cchWord = 0; } } // Keep the length so far of the last word found. cchLastWord = cchWord; if (cchLastWord > cchWordMax) { // Keep the length of the greatest length word found so far. cchWordMax = cchLastWord; } if (stringBuilder == null) stringBuilder = new StringBuilder(); // Gathering the raw text lsrun.CharacterBuffer.AppendToStringBuilder(stringBuilder, ichFirst, cch); // Keep the map between indices to raw text and its correspondent LSCP textVector.Set(cchText, cch, lscp - lscpChunk); cchText += cch; if (cch < cchLim) { // End of chunk is found lscp += cch; break; } } Invariant.Assert(lscpLim > lscp); lscp = lscpLim; } while (cchText < MaxCchWordToHyphenate); if (stringBuilder == null) return null; lscchChunk = lscp - lscpChunk; Invariant.Assert(stringBuilder.Length == cchText); char[] rawText = new char[stringBuilder.Length]; stringBuilder.CopyTo(0, rawText, 0, rawText.Length); return rawText; } /// /// Fetch cached inline metrics /// /// text object to format /// firs cp of text object /// inline's current pen position /// line's right margin ///inline info ////// Right margin is not necessarily the same as column max width. Right margin /// is usually greater than actual column width during object formatting. LS /// increases the margin to 1/32 of the column width to provide a leaway for /// breaking. /// /// However TextBlock/TextFlow functions in such a way that it needs to know the exact width /// left in the line in order to compute the inline's correct size. We make sure /// that it'll never get oversize max width. /// /// Inline object's reported size can be so huge that it may overflow LS's maximum value. /// If a given width is a finite value, we'll respect that and out-of-range exception may be thrown as appropriate. /// If the width is Positive Infinity, the width is trimmed to the maximum remaining value that LS can handle. This is /// appropriate for the cases where client measures inline objects at Infinite size. /// internal TextEmbeddedObjectMetrics FormatTextObject( TextEmbeddedObject textObject, int cpFirst, int currentPosition, int rightMargin ) { if(_textObjectMetricsVector == null) { _textObjectMetricsVector = new SpanVector(null); } SpanRider rider = new SpanRider(_textObjectMetricsVector); rider.At(cpFirst); TextEmbeddedObjectMetrics metrics = (TextEmbeddedObjectMetrics)rider.CurrentElement; if(metrics == null) { int widthLeft = _formatWidth - currentPosition; if(widthLeft <= 0) { // we're formatting this object outside the actual column width, // we give the host the max width from the current position up // to the margin. widthLeft = rightMargin - _formatWidth; } metrics = textObject.Format(_settings.Formatter.IdealToReal(widthLeft)); if (Double.IsPositiveInfinity(metrics.Width)) { // If the inline object has Width to be positive infinity, trim the width to // the maximum value that LS can handle. metrics = new TextEmbeddedObjectMetrics( _settings.Formatter.IdealToReal((Constants.IdealInfiniteWidth - currentPosition)), metrics.Height, metrics.Baseline ); } else if (metrics.Width > _settings.Formatter.IdealToReal((Constants.IdealInfiniteWidth - currentPosition))) { // LS cannot compute value greater than its maximum computable value throw new ArgumentException(SR.Get(SRID.TextObjectMetrics_WidthOutOfRange)); } _textObjectMetricsVector.SetReference(cpFirst, textObject.Length, metrics); } Debug.Assert(metrics != null); return metrics; } #region ENUMERATIONS & CONST // first negative cp of bullet marker internal const int LscpFirstMarker = (-0x7FFFFFFF); // Note: Trident uses this figure internal const int TypicalCharactersPerLine = 100; internal const char CharLineSeparator = '\x2028'; internal const char CharParaSeparator = '\x2029'; internal const char CharLineFeed = '\x000a'; internal const char CharCarriageReturn= '\x000d'; // Hardcoded strings in LS memory // They are kept as a pointer such that it would be fast to return // them as pointers back into LS. internal static IntPtr PwchParaSeparator; internal static IntPtr PwchLineSeparator; internal static IntPtr PwchNbsp; internal static IntPtr PwchHidden; internal static IntPtr PwchObjectTerminator; internal static IntPtr PwchObjectReplacement; /// !! DO NOT update this enum without looking at its unmanaged pair in lslo.cpp !! /// [wchao, 10-1-2001] // internal enum ObjectId : ushort { Reverse = 0, MaxNative = 1, InlineObject = 1, Max = 2, Text_chp = 0xffff, } // Maximum number of characters per line. If we exceed this number of characters // without breaking in a normal way, we chop off the line by generating a fake // line break run. This is to mitigate potential denial-of-service attacks by // ensuring that there is a reasonable upper bound on the time required to // format a single line. // // In ordinary documents with word-wrap enabled, we should always reach the // right margin long before MaxCharactersPerLine characters. The only reason // we might forcibly break the line in such cases would be: // (a) Extreme right margin // (b) Extreme number of zero-width characters // (c) Extremely small font size (i.e., fraction of a pixel) // (d) Lack of break opportunity (e.g., no spaces) // All of these are security cases, for which chopping off the line is a // reasonable mitigation. Limiting the line length addresses all of these // potential attacks so we don't need separate mitigations for, e.g., // zero-width characters. // // Extremely long lines are less unlikely in nowrap scenarios, such as a code // editor. However, the same security issues apply so we still chop off the line // if we exceed the maximum number of characters with no line break. Note that // Notepad (with nowrap) does the same thing. // // The value chosen corresponds roughly to four pages of text at 60 characters // per line and 40 lines per page. Testing shows this to be a resonable limit // in terms of run time. private const int MaxCharactersPerLine = 9600; // 60 * 40 * 4 ////// The maximum number of characters within a single word that is still considered a legitimate /// input for hyphenation. This value is suggested by Stefanie Schiller - the NLG expert when /// considering a theoretical example of a German compound word which consists of 12 compound /// segments. The following is that word. /// /// "DONAUDAMPFSCHIFFAHRTSELEKTRIZITAETENHAUPTBETRIEBSWERKBAUUNTERBEAMTENGESELLSCHAFT" /// /// [Wchao, 3/15/2006] /// private const int MaxCchWordToHyphenate = 80; #endregion #region Properties internal FormatSettings Settings { get { return _settings; } } internal ParaProp Pap { get { return _settings.Pap; } } internal int CpFirst { get { return _cpFirst; } } internal SpanVector PlsrunVector { get { return _plsrunVector; } } internal ArrayList LsrunList { get { return _lsrunList; } } internal int FormatWidth { get { return _formatWidth; } } internal int CchEol { get { return _cchEol; } set { _cchEol = value; } } #endregion } ////// Bidi state that applie across line. If no preceding state is available internally, /// it calls back to the client to obtain additional Bidi control and explicit embedding level. /// internal sealed class BidiState : Bidi.State { public BidiState(FormatSettings settings, int cpFirst) : this(settings, cpFirst, null) { } public BidiState(FormatSettings settings, int cpFirst, TextModifierScope modifierScope) : base (settings.Pap.RightToLeft) { _settings = settings; _cpFirst = cpFirst; NumberClass = DirectionClass.ClassInvalid; StrongCharClass = DirectionClass.ClassInvalid; // find the top most scope that has the direction embedding while ( modifierScope != null && !modifierScope.TextModifier.HasDirectionalEmbedding) { modifierScope = modifierScope.ParentScope; } if (modifierScope != null) { _cpFirstScope = modifierScope.TextSourceCharacterIndex; // Initialize Bidi stack base on modifier scope Bidi.BidiStack stack = new Bidi.BidiStack(); stack.Init(LevelStack); ushort overflowLevels = 0; InitLevelStackFromModifierScope(stack, modifierScope, ref overflowLevels); LevelStack = stack.GetData(); Overflow = overflowLevels; } } ////// Set the default last strongs when an embedding level is changed such that /// ambiguous characters (i.e. characters with null or InvariantCulture) at the beginning /// of the current embedding level can be resolved correctly. /// internal void SetLastDirectionClassesAtLevelChange() { if ((CurrentLevel & 1) == 0) { LastStrongClass = DirectionClass.Left; LastNumberClass = DirectionClass.Left; } else { LastStrongClass = DirectionClass.ArabicLetter; LastNumberClass = DirectionClass.ArabicNumber; } } internal byte CurrentLevel { get { return Bidi.BidiStack.GetMaximumLevel(LevelStack); } } ////// Method to get the last number class overridden by bidi algorithm implementer /// public override DirectionClass LastNumberClass { get { if (this.NumberClass == DirectionClass.ClassInvalid ) { GetLastDirectionClasses(); } return this.NumberClass; } set { this.NumberClass = value; } } ////// Method to get the last strong class overridden by bidi algorithm implementer /// public override DirectionClass LastStrongClass { get { if (this.StrongCharClass == DirectionClass.ClassInvalid) { GetLastDirectionClasses(); } return this.StrongCharClass; } set { this.StrongCharClass = value; this.NumberClass = value; } } ////// Last strong class not found internally, call out to client /// private void GetLastDirectionClasses() { DirectionClass strongClass = DirectionClass.ClassInvalid; DirectionClass numberClass = DirectionClass.ClassInvalid; // It is a flag to indicate whether to continue calling GetPrecedingText. // Because Bidi algorithm works within a paragraph only, we should terminate the // loop at paragraph boundary and fall back to the appropriate defaults. bool continueScanning = true; while (continueScanning && _cpFirst > _cpFirstScope) { TextSpantextSpan = _settings.GetPrecedingText(_cpFirst); CultureSpecificCharacterBufferRange charString = textSpan.Value; if (textSpan.Length <= 0) { break; // stop when preceding text span has length 0. } if (!charString.CharacterBufferRange.IsEmpty) { continueScanning = Bidi.GetLastStongAndNumberClass( charString.CharacterBufferRange, ref strongClass, ref numberClass ); if (strongClass != DirectionClass.ClassInvalid) { this.StrongCharClass = strongClass; if (this.NumberClass == DirectionClass.ClassInvalid) { if (numberClass == DirectionClass.EuropeanNumber) { // Override EuropeanNumber class as appropriate. numberClass = GetEuropeanNumberClassOverride(CultureMapper.GetSpecificCulture(charString.CultureInfo)); } this.NumberClass = numberClass; } break; } } _cpFirst -= textSpan.Length; } // If we don't have the strong class and/or number class, select appropriate defaults // according to the base bidi level. // // To determine the base bidi level, we look at bit 0 if the LevelStack. This is NOT // an even/odd test. LevelStack is an array of bits corresponding to all of the bidl // levels on the stack. Thus, bit 0 is set if and only if the base bidi level is zero, // i.e., it's a left-to-right paragraph. if(strongClass == DirectionClass.ClassInvalid) { this.StrongCharClass = ((CurrentLevel & 1) == 0) ? DirectionClass.Left : DirectionClass.ArabicLetter; } if(numberClass == DirectionClass.ClassInvalid) { this.NumberClass = ((CurrentLevel & 1) == 0) ? DirectionClass.Left : DirectionClass.ArabicNumber; } } /// /// Walk the TextModifierScope to reinitialize the bidi stack. /// We push to bidi-stack from the earliest directional modifier (i.e. from bottom of the /// the scope chain onwards). We use a stack to reverse the scope chain first. /// private static void InitLevelStackFromModifierScope( Bidi.BidiStack stack, TextModifierScope scope, ref ushort overflowLevels ) { StackdirectionalEmbeddingStack = new Stack (32); for (TextModifierScope currentScope = scope; currentScope != null; currentScope = currentScope.ParentScope) { if (currentScope.TextModifier.HasDirectionalEmbedding) { directionalEmbeddingStack.Push(currentScope.TextModifier); } } while (directionalEmbeddingStack.Count > 0) { TextModifier modifier = directionalEmbeddingStack.Pop(); if (overflowLevels > 0) { // Bidi level stack overflows. Just increment the bidi stack overflow number overflowLevels ++; } else if (!stack.Push(modifier.FlowDirection == FlowDirection.LeftToRight)) { // Push stack not successful. Stack starts to overflow. overflowLevels = 1; } } } /// /// Obtain the explict direction class of European number based on culture and current flow direction. /// European numbers in Arabic/---- culture and RTL flow direction are to be considered as Arabic numbers. /// internal DirectionClass GetEuropeanNumberClassOverride(CultureInfo cultureInfo) { if ( cultureInfo != null &&( (cultureInfo.LCID & 0xFF) == 0x01 // Arabic culture || (cultureInfo.LCID & 0xFF) == 0x29 // ---- culture ) && (CurrentLevel & 1) != 0 // RTL flow direction ) { return DirectionClass.ArabicNumber; } return DirectionClass.EuropeanNumber; } private FormatSettings _settings; private int _cpFirst; private int _cpFirstScope; // The first Cp of the current scope. GetLastStrong() should not go beyond it. } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- IsolatedStorageFileStream.cs
- WebPartConnectionsCancelEventArgs.cs
- ItemsPresenter.cs
- RangeBaseAutomationPeer.cs
- ThicknessAnimationUsingKeyFrames.cs
- XPathDocument.cs
- HtmlLinkAdapter.cs
- ResourceExpressionBuilder.cs
- CopyAttributesAction.cs
- RemoteCryptoSignHashRequest.cs
- BooleanConverter.cs
- EdmToObjectNamespaceMap.cs
- SqlTopReducer.cs
- OptionalColumn.cs
- GridPattern.cs
- MultiView.cs
- SendingRequestEventArgs.cs
- _CookieModule.cs
- GeneralTransform3DGroup.cs
- UnknownBitmapDecoder.cs
- ReversePositionQuery.cs
- ParameterBuilder.cs
- ImageFormatConverter.cs
- DataGridViewCellValidatingEventArgs.cs
- Shape.cs
- Typeface.cs
- Invariant.cs
- ColorMatrix.cs
- CustomErrorCollection.cs
- DoubleCollectionConverter.cs
- DesignOnlyAttribute.cs
- XmlSchemaImport.cs
- MultiBinding.cs
- BrushValueSerializer.cs
- ParagraphResult.cs
- WindowsListViewGroup.cs
- InlineObject.cs
- FontCollection.cs
- DataGridViewButtonCell.cs
- StringValidator.cs
- AnimationClock.cs
- PageSettings.cs
- OledbConnectionStringbuilder.cs
- BooleanSwitch.cs
- UInt32Storage.cs
- HandlerMappingMemo.cs
- DocumentPageView.cs
- ScriptControlDescriptor.cs
- InstanceOwnerException.cs
- HwndKeyboardInputProvider.cs
- HostingEnvironmentSection.cs
- WebMessageEncodingElement.cs
- Visual.cs
- IndentedTextWriter.cs
- CompilerErrorCollection.cs
- WebPartHelpVerb.cs
- CompiledQueryCacheEntry.cs
- WebServiceMethodData.cs
- AccessorTable.cs
- AudioLevelUpdatedEventArgs.cs
- SafeRegistryKey.cs
- WebPartChrome.cs
- GenericWebPart.cs
- ContextQuery.cs
- DataGridParentRows.cs
- DocumentViewer.cs
- EntityProviderServices.cs
- NonBatchDirectoryCompiler.cs
- NotificationContext.cs
- FixedFlowMap.cs
- Set.cs
- SignatureDescription.cs
- BuildManagerHost.cs
- DbConnectionPoolOptions.cs
- InfocardExtendedInformationEntry.cs
- InkPresenterAutomationPeer.cs
- ItemDragEvent.cs
- PathFigureCollection.cs
- Random.cs
- SecurityManager.cs
- CqlWriter.cs
- ProfileSection.cs
- TreeViewImageIndexConverter.cs
- FormsAuthenticationEventArgs.cs
- RootProfilePropertySettingsCollection.cs
- CultureInfoConverter.cs
- SiblingIterators.cs
- DataGridViewIntLinkedList.cs
- XPathAncestorIterator.cs
- FigureParagraph.cs
- DataGridRowClipboardEventArgs.cs
- ToolboxItem.cs
- AdapterUtil.cs
- TextDecorationCollection.cs
- Point3DIndependentAnimationStorage.cs
- __Error.cs
- DoubleUtil.cs
- XmlSchemaAnyAttribute.cs
- FontStyleConverter.cs
- ClientRuntimeConfig.cs