/// <summary> /// Compute unshaped glyph run object from the specified character-based info /// </summary> internal sealed override GlyphRun ComputeUnshapedGlyphRun( Point origin, char[] characterString, IList <double> characterAdvances ) { bool nullFont; GlyphTypeface glyphTypeface = GetGlyphTypeface(out nullFont); Invariant.Assert(glyphTypeface != null); return(glyphTypeface.ComputeUnshapedGlyphRun( origin, new CharacterBufferRange( characterString, 0, // offsetToFirstChar characterString.Length ), characterAdvances, _emSize, (float)_properties.PixelsPerDip, _properties.FontHintingEmSize, nullFont, CultureMapper.GetSpecificCulture(_properties.CultureInfo), (_shapeTypeface == null || _shapeTypeface.DeviceFont == null) ? null : _shapeTypeface.DeviceFont.Name, _textFormattingMode )); }
/// <summary> /// Compute a shaped glyph run object from specified glyph-based info /// </summary> internal sealed override GlyphRun ComputeShapedGlyphRun( Point origin, char[] characterString, ushort[] clusterMap, ushort[] glyphIndices, IList <double> glyphAdvances, IList <Point> glyphOffsets, bool rightToLeft, bool sideways ) { Invariant.Assert(_shapeTypeface != null); Invariant.Assert(glyphIndices != null); // Device fonts are only used through the LS non-glyphed code path. Only when a DigitCulture is set // will a potential device font be ignored and come through shaping. Invariant.Assert(_shapeTypeface.DeviceFont == null || _textItem.DigitCulture != null); bool[] caretStops = null; if (clusterMap != null && (HasExtendedCharacter || NeedsCaretInfo) ) { caretStops = new bool[clusterMap.Length + 1]; // caret stops at cluster boundaries, the first and the last entries are always set caretStops[0] = true; caretStops[clusterMap.Length] = true; ushort lastGlyph = clusterMap[0]; for (int i = 1; i < clusterMap.Length; i++) { ushort glyph = clusterMap[i]; if (glyph != lastGlyph) { caretStops[i] = true; lastGlyph = glyph; } } } return(GlyphRun.TryCreate( _shapeTypeface.GlyphTypeface, (rightToLeft ? 1 : 0), sideways, _emSize, glyphIndices, origin, glyphAdvances, glyphOffsets, characterString, null, clusterMap, caretStops, XmlLanguage.GetLanguage(CultureMapper.GetSpecificCulture(_properties.CultureInfo).IetfLanguageTag), _textFormattingMode )); }
private static CultureInfo GetNumberCulture(TextRunProperties properties, out NumberSubstitutionMethod method, out bool ignoreUserOverride) { ignoreUserOverride = true; NumberSubstitution sub = properties.NumberSubstitution; if (sub == null) { method = NumberSubstitutionMethod.AsCulture; return(CultureMapper.GetSpecificCulture(properties.CultureInfo)); } method = sub.Substitution; switch (sub.CultureSource) { case NumberCultureSource.Text: return(CultureMapper.GetSpecificCulture(properties.CultureInfo)); case NumberCultureSource.User: ignoreUserOverride = false; return(CultureInfo.CurrentCulture); case NumberCultureSource.Override: return(sub.CultureOverride); } return(null); }
/// <summary> /// Get text immediately preceding cpLimit. /// </summary> internal TextSpan <CultureSpecificCharacterBufferRange> GetPrecedingText(TextSource textSource, int cpLimit) { if (cpLimit > 0) { SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition); if (textRunSpanRider.At(cpLimit - 1)) { CharacterBufferRange charString = CharacterBufferRange.Empty; CultureInfo culture = null; TextRun run = textRunSpanRider.CurrentElement as TextRun; if (run != null) { // Only TextRun containing text would have non-empty Character buffer range. if (TextRunInfo.GetRunType(run) == Plsrun.Text && run.CharacterBufferReference.CharacterBuffer != null) { charString = new CharacterBufferRange( run.CharacterBufferReference, cpLimit - textRunSpanRider.CurrentSpanStart); culture = CultureMapper.GetSpecificCulture(run.Properties.CultureInfo); } return(new TextSpan <CultureSpecificCharacterBufferRange>( cpLimit - textRunSpanRider.CurrentSpanStart, // cp length new CultureSpecificCharacterBufferRange(culture, charString) )); } } } // not in cache so call back to client return(textSource.GetPrecedingText(cpLimit)); }
/// <summary> /// Break a run of text into individually shape items. /// Shape items are delimited by /// Change of writing system /// Change of glyph typeface /// </summary> IList <TextShapeableSymbols> ITextSymbols.GetTextShapeableSymbols( GlyphingCache glyphingCache, CharacterBufferReference characterBufferReference, int length, bool rightToLeft, bool isRightToLeftParagraph, CultureInfo digitCulture, TextModifierScope textModifierScope, TextFormattingMode textFormattingMode, bool isSideways ) { if (characterBufferReference.CharacterBuffer == null) { throw new ArgumentNullException("characterBufferReference.CharacterBuffer"); } int offsetToFirstChar = characterBufferReference.OffsetToFirstChar - _characterBufferReference.OffsetToFirstChar; Debug.Assert(characterBufferReference.CharacterBuffer == _characterBufferReference.CharacterBuffer); Debug.Assert(offsetToFirstChar >= 0 && offsetToFirstChar < _length); if (length < 0 || offsetToFirstChar + length > _length) { length = _length - offsetToFirstChar; } // Get the actual text run properties in effect, after invoking any // text modifiers that may be in scope. TextRunProperties textRunProperties = _textRunProperties; if (textModifierScope != null) { textRunProperties = textModifierScope.ModifyProperties(textRunProperties); } if (!rightToLeft) { // Fast loop early out for run with all non-complex characters // which can be optimized by not going thru shaping engine. int nominalLength; if (textRunProperties.Typeface.CheckFastPathNominalGlyphs( new CharacterBufferRange(characterBufferReference, length), textRunProperties.FontRenderingEmSize, (float)textRunProperties.PixelsPerDip, 1.0, double.MaxValue, // widthMax true, // keepAWord digitCulture != null, CultureMapper.GetSpecificCulture(textRunProperties.CultureInfo), textFormattingMode, isSideways, false, //breakOnTabs out nominalLength ) && length == nominalLength) { return(new TextShapeableCharacters[] { new TextShapeableCharacters( new CharacterBufferRange(characterBufferReference, nominalLength), textRunProperties, textRunProperties.FontRenderingEmSize, new MS.Internal.Text.TextInterface.ItemProps(), null, // shapeTypeface (no shaping required) false, // nullShape, textFormattingMode, isSideways ) }); } } IList <TextShapeableSymbols> shapeables = new List <TextShapeableSymbols>(2); glyphingCache.GetShapeableText( textRunProperties.Typeface, characterBufferReference, length, textRunProperties, digitCulture, isRightToLeftParagraph, shapeables, this as IShapeableTextCollector, textFormattingMode ); return(shapeables); }
/// <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); }