/// <summary> /// Client to notify change in part of the cache when text or /// properties of the run is being added, removed or replaced. /// </summary> /// <remarks> /// The client's expectation of this notification is that TextFormatter /// retains valid state for run cache in response to this change. It's /// expected that at least TextFormatter will refetch the runs affected /// by the change. Subsequent runs may or may not be refected depending /// on the state of the cache after the change. /// </remarks> public void Change( int textSourceCharacterIndex, int addition, int removal ) { if (textSourceCharacterIndex < 0) return; int cchActive = 0; for (int i = 0; i < _textRunVector.Count; i++) cchActive += _textRunVector[i].length; if (textSourceCharacterIndex >= cchActive) return; SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, textSourceCharacterIndex); _latestPosition = textRunSpanRider.SpanPosition; // we remove runs from the cache starting from the one containing the change // to the end of the active range. We do not try to interpret the scope of // the change and try to minimize the range in which the cache is invalidated, // because that would require an in-depth understanding of how our client // implement their formatting change mechanism and how they respond to future // refetch after the change which could vary among different clients. That // kind of work is beyond the purpose of this notification. _latestPosition = _textRunVector.SetValue( textRunSpanRider.CurrentSpanStart, cchActive - textRunSpanRider.CurrentSpanStart, _textRunVector.Default, _latestPosition ); }
internal void SetSpanBaseInColumn(IEasyGridCell baseCell, int rowSpan = 1) { if (rowSpan <= 1) { if (baseCell.Equals(_targetCell)) { SpanPos = SpanPosition.NoSpan; _rowSpanSize = 1; _colSpanSize = 1; } return; } if (baseCell.Equals(_targetCell)) { SpanBaseCell = baseCell; SpanPos = SpanPosition.SpanBase; _rowSpanSize = rowSpan; _colSpanSize = 1; } else if (_targetCell.RowIndex > baseCell.RowIndex && _targetCell.RowIndex <= baseCell.RowIndex + rowSpan) { SpanBaseCell = baseCell; SpanPos = SpanPosition.Spanned; _rowSpanSize = 0; } else { //이 영역에 들어가지 않으므로 상태를 유지한다. } }
/// <summary> /// cell에서 부터 시작해서 Row 안에서 몇 개의 column을 span할 것인지 지정한다. /// 대상 cell이 이 셋팅으로 인하여 어느 위치에 어떻게 들어가는지를 나타낸다. /// </summary> /// <param name="cell"></param> /// <param name="colSpan">1이면 span하지 않는다는 뜻이다.</param> internal void SetSpanBaseInRow(IEasyGridCell cell, int colSpan = 1) { if (colSpan <= 1) { if (cell.Equals(_targetCell)) { SpanPos = SpanPosition.NoSpan; _colSpanSize = 1; } return; } if (cell.Equals(_targetCell)) { SpanBaseCell = cell; SpanPos = SpanPosition.SpanBase; _colSpanSize = colSpan; _rowSpanSize = 1; } else if (_targetCell.ColumnIndex > cell.ColumnIndex && _targetCell.ColumnIndex < cell.ColumnIndex + colSpan) { SpanBaseCell = cell; SpanPos = SpanPosition.Spanned; _colSpanSize = 0; } else { //이 영역에 들어가지 않으므로 상태를 유지한다. } }
/// <summary> /// Client to notify change in part of the cache when text or /// properties of the run is being added, removed or replaced. /// </summary> /// <remarks> /// The client's expectation of this notification is that TextFormatter /// retains valid state for run cache in response to this change. It's /// expected that at least TextFormatter will refetch the runs affected /// by the change. Subsequent runs may or may not be refected depending /// on the state of the cache after the change. /// </remarks> public void Change( int textSourceCharacterIndex, int addition, int removal ) { if (textSourceCharacterIndex < 0) { return; } int cchActive = 0; for (int i = 0; i < _textRunVector.Count; i++) { cchActive += _textRunVector[i].length; } if (textSourceCharacterIndex >= cchActive) { return; } SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, textSourceCharacterIndex); _latestPosition = textRunSpanRider.SpanPosition; // we remove runs from the cache starting from the one containing the change // to the end of the active range. We do not try to interpret the scope of // the change and try to minimize the range in which the cache is invalidated, // because that would require an in-depth understanding of how our client // implement their formatting change mechanism and how they respond to future // refetch after the change which could vary among different clients. That // kind of work is beyond the purpose of this notification. _latestPosition = _textRunVector.SetValue( textRunSpanRider.CurrentSpanStart, cchActive - textRunSpanRider.CurrentSpanStart, _textRunVector.Default, _latestPosition ); }
/// <summary> /// Default constructor used internally /// </summary> /// <remarks> /// Text layout client never create their own run cache as its implementation /// should be opaque to them. /// </remarks> internal TextRunCacheImp() { _textRunVector = new SpanVector(null); _latestPosition = new SpanPosition(); }
/// <summary> /// Fetch cached textrun /// </summary> internal TextRun FetchTextRun( FormatSettings settings, int cpFetch, int cpFirst, out int offsetToFirstCp, out int runLength ) { SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, cpFetch); _latestPosition = textRunSpanRider.SpanPosition; TextRun textRun = (TextRun)textRunSpanRider.CurrentElement; if(textRun == null) { // run not already cached, fetch new run and cache it textRun = settings.TextSource.GetTextRun(cpFetch); if (textRun.Length < 1) { throw new ArgumentOutOfRangeException("textRun.Length", SR.Get(SRID.ParameterMustBeGreaterThanZero)); } Plsrun plsrun = TextRunInfo.GetRunType(textRun); if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject) { TextRunProperties properties = textRun.Properties; if (properties == null) throw new ArgumentException(SR.Get(SRID.TextRunPropertiesCannotBeNull)); if (properties.FontRenderingEmSize <= 0) throw new ArgumentException(SR.Get(SRID.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties")); double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm; if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize) throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize)); CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo); if (culture == null) throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties")); if (properties.Typeface == null) throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties")); } // // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be // consistent with the positions encoded by SpanVector. In run cache, the begining of a span // should always correspond to the begining of a cached text run. If the end of the currently fetched // run overlaps with the begining of an already cached run, the begining of the cached run needs to be // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range // so we'll simply remove the overlapped runs here. // // Move the rider to the end of the current run textRunSpanRider.At(cpFetch + textRun.Length - 1); _latestPosition = textRunSpanRider.SpanPosition; if (textRunSpanRider.CurrentElement != _textRunVector.Default) { // The end overlaps with one or more cached runs, clear the range from the // begining of the current fetched run to the end of the last overlapped cached run. _latestPosition = _textRunVector.SetReference( cpFetch, textRunSpanRider.CurrentPosition + textRunSpanRider.Length - cpFetch, _textRunVector.Default, _latestPosition ); } _latestPosition = _textRunVector.SetReference(cpFetch, textRun.Length, textRun, _latestPosition); // Refresh the rider's SpanPosition following previous SpanVector.SetReference calls textRunSpanRider.At(_latestPosition, cpFetch); } offsetToFirstCp = textRunSpanRider.CurrentPosition - textRunSpanRider.CurrentSpanStart; runLength = textRunSpanRider.Length; Debug.Assert(textRun != null && runLength > 0, "Invalid run!"); bool isText = textRun is ITextSymbols; if (isText) { // Chop text run to optimal length so we dont spend forever analysing // them all at once. int looseCharLength = TextStore.TypicalCharactersPerLine - cpFetch + cpFirst; if(looseCharLength <= 0) { // this line already exceeds typical line length, incremental fetch goes // about a quarter of the typical length. looseCharLength = (int)Math.Round(TextStore.TypicalCharactersPerLine * 0.25); } if(runLength > looseCharLength) { if (TextRunInfo.GetRunType(textRun) == Plsrun.Text) { // // When chopping the run at the typical line length, // - don't chop in between of higher & lower surrogate // - don't chop combining mark away from its base character // - don't chop joiner from surrounding characters // // Starting from the initial chopping point, we look ahead to find a safe position. We stop at // a limit in case the run consists of many combining mark & joiner. That is rare and doesn't make // much sense in shaping already. // CharacterBufferReference charBufferRef = textRun.CharacterBufferReference; // We look ahead by one more line at most. It is not normal to have // so many combining mark or joiner characters in a row. It doesn't make sense to // look further if so. int lookAheadLimit = Math.Min(runLength, looseCharLength + TextStore.TypicalCharactersPerLine); int sizeOfChar = 0; int endOffset = 0; bool canBreakAfterPrecedingChar = false; for (endOffset = looseCharLength - 1; endOffset < lookAheadLimit; endOffset += sizeOfChar) { CharacterBufferRange charString = new CharacterBufferRange( charBufferRef.CharacterBuffer, charBufferRef.OffsetToFirstChar + offsetToFirstCp + endOffset, runLength - endOffset ); int ch = Classification.UnicodeScalar(charString, out sizeOfChar); // We can only safely break if the preceding char is not a joiner character (i.e. can-break-after), // and the current char is not combining or joiner (i.e. can-break-before). if (canBreakAfterPrecedingChar && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch) ) { break; } canBreakAfterPrecedingChar = !Classification.IsJoiner(ch); } looseCharLength = Math.Min(runLength, endOffset); } runLength = looseCharLength; } } Debug.Assert( // valid run found runLength > 0 // non-text run always fetched at run start && ( isText || textRunSpanRider.CurrentSpanStart - textRunSpanRider.CurrentPosition == 0) // span rider of both text and format point to valid position && (textRunSpanRider.Length > 0 && textRunSpanRider.CurrentElement != null), "Text run fetching error!" ); return textRun; }
/// <summary> /// Fetch cached textrun /// </summary> internal TextRun FetchTextRun( FormatSettings settings, int cpFetch, int cpFirst, out int offsetToFirstCp, out int runLength ) { SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, cpFetch); _latestPosition = textRunSpanRider.SpanPosition; TextRun textRun = (TextRun)textRunSpanRider.CurrentElement; if (textRun == null) { // run not already cached, fetch new run and cache it textRun = settings.TextSource.GetTextRun(cpFetch); if (textRun.Length < 1) { throw new ArgumentOutOfRangeException("textRun.Length", SR.Get(SRID.ParameterMustBeGreaterThanZero)); } Plsrun plsrun = TextRunInfo.GetRunType(textRun); if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject) { TextRunProperties properties = textRun.Properties; if (properties == null) { throw new ArgumentException(SR.Get(SRID.TextRunPropertiesCannotBeNull)); } if (properties.FontRenderingEmSize <= 0) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties")); } double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm; if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize)); } CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo); if (culture == null) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties")); } if (properties.Typeface == null) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties")); } } // // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be // consistent with the positions encoded by SpanVector. In run cache, the begining of a span // should always correspond to the begining of a cached text run. If the end of the currently fetched // run overlaps with the begining of an already cached run, the begining of the cached run needs to be // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range // so we'll simply remove the overlapped runs here. // // Move the rider to the end of the current run textRunSpanRider.At(cpFetch + textRun.Length - 1); _latestPosition = textRunSpanRider.SpanPosition; if (textRunSpanRider.CurrentElement != _textRunVector.Default) { // The end overlaps with one or more cached runs, clear the range from the // begining of the current fetched run to the end of the last overlapped cached run. _latestPosition = _textRunVector.SetReference( cpFetch, textRunSpanRider.CurrentPosition + textRunSpanRider.Length - cpFetch, _textRunVector.Default, _latestPosition ); } _latestPosition = _textRunVector.SetReference(cpFetch, textRun.Length, textRun, _latestPosition); // Refresh the rider's SpanPosition following previous SpanVector.SetReference calls textRunSpanRider.At(_latestPosition, cpFetch); } // If the TextRun was obtained from the cache, make sure it has the right PixelsPerDip set on its properties. if (textRun.Properties != null) { textRun.Properties.PixelsPerDip = settings.TextSource.PixelsPerDip; } offsetToFirstCp = textRunSpanRider.CurrentPosition - textRunSpanRider.CurrentSpanStart; runLength = textRunSpanRider.Length; Debug.Assert(textRun != null && runLength > 0, "Invalid run!"); bool isText = textRun is ITextSymbols; if (isText) { // Chop text run to optimal length so we dont spend forever analysing // them all at once. int looseCharLength = TextStore.TypicalCharactersPerLine - cpFetch + cpFirst; if (looseCharLength <= 0) { // this line already exceeds typical line length, incremental fetch goes // about a quarter of the typical length. looseCharLength = (int)Math.Round(TextStore.TypicalCharactersPerLine * 0.25); } if (runLength > looseCharLength) { if (TextRunInfo.GetRunType(textRun) == Plsrun.Text) { // // When chopping the run at the typical line length, // - don't chop in between of higher & lower surrogate // - don't chop combining mark away from its base character // - don't chop joiner from surrounding characters // // Starting from the initial chopping point, we look ahead to find a safe position. We stop at // a limit in case the run consists of many combining mark & joiner. That is rare and doesn't make // much sense in shaping already. // CharacterBufferReference charBufferRef = textRun.CharacterBufferReference; // We look ahead by one more line at most. It is not normal to have // so many combining mark or joiner characters in a row. It doesn't make sense to // look further if so. int lookAheadLimit = Math.Min(runLength, looseCharLength + TextStore.TypicalCharactersPerLine); int sizeOfChar = 0; int endOffset = 0; bool canBreakAfterPrecedingChar = false; for (endOffset = looseCharLength - 1; endOffset < lookAheadLimit; endOffset += sizeOfChar) { CharacterBufferRange charString = new CharacterBufferRange( charBufferRef.CharacterBuffer, charBufferRef.OffsetToFirstChar + offsetToFirstCp + endOffset, runLength - endOffset ); int ch = Classification.UnicodeScalar(charString, out sizeOfChar); // We can only safely break if the preceding char is not a joiner character (i.e. can-break-after), // and the current char is not combining or joiner (i.e. can-break-before). if (canBreakAfterPrecedingChar && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch)) { break; } canBreakAfterPrecedingChar = !Classification.IsJoiner(ch); } looseCharLength = Math.Min(runLength, endOffset); } runLength = looseCharLength; } } Debug.Assert( // valid run found runLength > 0 // non-text run always fetched at run start && (isText || textRunSpanRider.CurrentSpanStart - textRunSpanRider.CurrentPosition == 0) // span rider of both text and format point to valid position && (textRunSpanRider.Length > 0 && textRunSpanRider.CurrentElement != null), "Text run fetching error!" ); return(textRun); }