public static int GetLength(this TextLine textLine, TextRun endOfLine) { int length = textLine.Length; var textRuns = textLine.GetTextRunSpans(); if (textRuns.Count > 0 && textRuns[textRuns.Count - 1].Value == endOfLine) return length - endOfLine.Length; return length; }
public NormalizedSpan(TextRun textRun, Int32 startCharacterIndex) { _textRun = textRun; _length = _textRun.Length; _startCharacterIndex = startCharacterIndex; _canSplitOrMerge = false; }
/// <summary> /// Construct a text trailing word ellipsis collapsing properties /// </summary> /// <param name="width">width in which collapsing is constrained to</param> /// <param name="textRunProperties">text run properties of ellispis symbol</param> public TextTrailingWordEllipsis( double width, TextRunProperties textRunProperties ) { _width = width; _ellipsis = new TextCharacters(StringHorizontalEllipsis, textRunProperties); }
public FormattedTextSymbols( GlyphingCache glyphingCache, TextRun textSymbols, bool rightToLeft, double scalingFactor, TextFormattingMode textFormattingMode, bool isSideways ) { _textFormattingMode = textFormattingMode; _isSideways = isSideways; ITextSymbols symbols = textSymbols as ITextSymbols; Debug.Assert(symbols != null); // break down a single text run into pieces IList<TextShapeableSymbols> shapeables = symbols.GetTextShapeableSymbols( glyphingCache, textSymbols.CharacterBufferReference, textSymbols.Length, rightToLeft, // This is a bool indicating the RTL // based on the bidi level of text (if applicable). // For FormattedTextSymbols it is equal to paragraph flow direction. rightToLeft, // This is the flow direction of the paragraph as // specified by the user. DWrite needs the paragraph // flow direction of the paragraph // while WPF algorithms need the RTL of the text based on // Bidi if possible. null, // cultureInfo null, // textModifierScope _textFormattingMode, _isSideways ); Debug.Assert(shapeables != null && shapeables.Count > 0); _rightToLeft = rightToLeft; _glyphs = new Glyphs[shapeables.Count]; CharacterBuffer charBuffer = textSymbols.CharacterBufferReference.CharacterBuffer; int offsetToFirstChar = textSymbols.CharacterBufferReference.OffsetToFirstChar; int i = 0; int ich = 0; while (i < shapeables.Count) { TextShapeableSymbols current = shapeables[i] as TextShapeableSymbols; Debug.Assert(current != null); int cch = current.Length; int j; // make a separate character buffer for glyphrun persistence char[] charArray = new char[cch]; for (j = 0; j < cch; j++) charArray[j] = charBuffer[offsetToFirstChar + ich + j]; if (current.IsShapingRequired) { ushort[] clusterMap; ushort[] glyphIndices; int[] glyphAdvances; GlyphOffset[] glyphOffsets; // unsafe { fixed (char* fixedCharArray = &charArray[0]) { MS.Internal.Text.TextInterface.TextAnalyzer textAnalyzer = MS.Internal.FontCache.DWriteFactory.Instance.CreateTextAnalyzer(); GlyphTypeface glyphTypeface = current.GlyphTypeFace; DWriteFontFeature[][] fontFeatures; uint[] fontFeatureRanges; uint unsignedCch = checked((uint)cch); LSRun.CompileFeatureSet(current.Properties.TypographyProperties, unsignedCch, out fontFeatures, out fontFeatureRanges); textAnalyzer.GetGlyphsAndTheirPlacements( (ushort*)fixedCharArray, unsignedCch, glyphTypeface.FontDWrite, glyphTypeface.BlankGlyphIndex, false, // no sideway support yet /************************************************************************************************/ // rightToLeft, current.Properties.CultureInfo, /************************************************************************************************/ fontFeatures, fontFeatureRanges, current.Properties.FontRenderingEmSize, scalingFactor, Util.PixelsPerDip, _textFormattingMode, current.ItemProps, out clusterMap, out glyphIndices, out glyphAdvances, out glyphOffsets ); } _glyphs[i] = new Glyphs( current, charArray, glyphAdvances, clusterMap, glyphIndices, glyphOffsets, scalingFactor ); } } else { // shaping not required, // bypass glyphing process altogether int[] nominalAdvances = new int[charArray.Length]; unsafe { fixed (char* fixedCharArray = &charArray[0]) fixed (int* fixedNominalAdvances = &nominalAdvances[0]) { current.GetAdvanceWidthsUnshaped( fixedCharArray, cch, scalingFactor, // format resolution specified per em, fixedNominalAdvances ); } } _glyphs[i] = new Glyphs( current, charArray, nominalAdvances, scalingFactor ); } i++; ich += cch; } }
/// <summary> /// Construct a character string object by extracting text info from text run /// </summary> internal CharacterBufferRange(TextRun textRun) { _charBufferRef = textRun.CharacterBufferReference; _length = textRun.Length; }
internal CharacterBufferRange FetchTextRun( int cpFetch, int cpFirst, out TextRun textRun, out int runLength ) { int offsetToFirstCp; textRun = _runCache.FetchTextRun(this, cpFetch, cpFirst, out offsetToFirstCp, out runLength); CharacterBufferRange charString; switch (TextRunInfo.GetRunType(textRun)) { case Plsrun.Text: { CharacterBufferReference charBufferRef = textRun.CharacterBufferReference; charString = new CharacterBufferRange( charBufferRef.CharacterBuffer, charBufferRef.OffsetToFirstChar + offsetToFirstCp, runLength ); break; } case Plsrun.InlineObject: Debug.Assert(offsetToFirstCp == 0); unsafe { charString = new CharacterBufferRange((char*) TextStore.PwchObjectReplacement, 1); } break; case Plsrun.LineBreak: Debug.Assert(offsetToFirstCp == 0); unsafe { // // Line break is to be represented as "Line Separator" such that // it doesn't terminate the optimal paragraph formatting session, but still breaks the line // unambiguously. // charString = new CharacterBufferRange((char*) TextStore.PwchLineSeparator, 1); } break; case Plsrun.ParaBreak: Debug.Assert(offsetToFirstCp == 0); unsafe { // // Paragraph break is special as it also terminates the paragraph. // It should be represented as "Paragraph Separator" such that it will be correctly handled // in Bidi and Optimal paragraph formatting. // charString = new CharacterBufferRange((char*) TextStore.PwchParaSeparator, 1); } break; case Plsrun.Hidden: unsafe { charString = new CharacterBufferRange((char*) TextStore.PwchHidden, 1); } break; default: charString = CharacterBufferRange.Empty; break; } return charString; }
/// <summary> /// Construct simple text run /// </summary> /// <param name="length">run length</param> /// <param name="textRun">text run</param> /// <param name="flags">run flags</param> private SimpleRun( int length, TextRun textRun, Flags flags, TextFormatterImp textFormatterImp ) { Length = length; TextRun = textRun; RunFlags = flags; _textFormatterImp = textFormatterImp; }
/// <summary> /// Create simple run of text, /// returning null if the specified text run cannot be correctly formatted as simple run /// </summary> static internal SimpleRun CreateSimpleTextRun( CharacterBufferRange charBufferRange, TextRun textRun, TextFormatterImp formatter, int widthLeft, bool emergencyWrap, bool breakOnTabs ) { Invariant.Assert(textRun is TextCharacters); SimpleRun run = new SimpleRun(formatter); run.CharBufferReference = charBufferRange.CharacterBufferReference; run.TextRun = textRun; if (!run.TextRun.Properties.Typeface.CheckFastPathNominalGlyphs( charBufferRange, run.EmSize, 1.0, formatter.IdealToReal(widthLeft), !emergencyWrap, false, CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo), formatter.TextFormattingMode, false, //No support for isSideways breakOnTabs, out run.Length )) { // Getting nominal glyphs is not supported by the font, // or it is but it results in low typographic quality text // e.g. OpenType support is not utilized. return null; } run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( new CharacterBufferRange(run.CharBufferReference, run.Length), run.EmSize, TextFormatterImp.ToIdeal, formatter.TextFormattingMode, false, out run.NominalAdvances, out run.IdealWidth ); return run; }
/// <summary> /// Returns a simple text run that represents a Tab. /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="textRun">text run</param> /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param> static private SimpleRun CreateSimpleRunForTab( FormatSettings settings, TextRun textRun, int idealRunOffsetUnRounded ) { if (settings == null || textRun == null || textRun.Properties == null || textRun.Properties.Typeface == null) { return null; } GlyphTypeface glyphTypeface = textRun.Properties.Typeface.TryGetGlyphTypeface(); // Check whether the font has the space character. If not then we have to go through // font fallback. // We are not calling CreateSimpleTextRun() because CheckFastPathNominalGlyphs() // can fail if a font has TypographicAvailabilities. We are simply rendering a space // so we don't realy care about TypographicFeatures. This is a perf optimization. if (glyphTypeface == null || !glyphTypeface.HasCharacter(' ')) { return null; } // The full shaping path converts tabs to spaces. // Note: In order to get exactly the same metrics as we did in FullTextLine (specifically ink bounding box) // we need to "Draw" a space in place of a Tab (previously we were just ignoring the Tab and rendering nothing) // which turned out to give different overhang and extent values than those returned using the full shaping path. // So in order to avoid vertical jiggling when a line is changed from SimpleTextLine to FullTextLine by adding/removing // a complex character, we need to do the same thing as the full shaping path and draw a space for each tab. TextRun modifedTextRun = new TextCharacters(" ", textRun.Properties); CharacterBufferRange characterBufferRange = new CharacterBufferRange(modifedTextRun); SimpleRun run = new SimpleRun(1, modifedTextRun, Flags.Tab, settings.Formatter); run.CharBufferReference = characterBufferRange.CharacterBufferReference; run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( characterBufferRange, run.EmSize, TextFormatterImp.ToIdeal, settings.Formatter.TextFormattingMode, false, out run.NominalAdvances ); int idealIncrementalTab = TextFormatterImp.RealToIdeal(settings.Pap.DefaultIncrementalTab); // Here we get the next tab stop without snapping the metrics to pixels. // We do the pixel snapping on the final position of the tab stop (and not on the IncrementalTab) // to achieve the same results as those in full shaping. int idealNextTabStopUnRounded = ((idealRunOffsetUnRounded / idealIncrementalTab) + 1) * idealIncrementalTab; run.IdealWidth = run.NominalAdvances[0] = idealNextTabStopUnRounded - idealRunOffsetUnRounded; return run; }
/// <summary> /// Creating a simple text run /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="charString">character string associated to textrun</param> /// <param name="textRun">text run</param> /// <param name="cp">first cp of the run</param> /// <param name="cpFirst">first cp of the line</param> /// <param name="runLength">run length</param> /// <param name="widthLeft">maximum run width</param> /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param> /// <returns>a SimpleRun object</returns> static public SimpleRun Create( FormatSettings settings, CharacterBufferRange charString, TextRun textRun, int cp, int cpFirst, int runLength, int widthLeft, int idealRunOffsetUnRounded ) { SimpleRun run = null; if (textRun is TextCharacters) { if ( textRun.Properties.BaselineAlignment != BaselineAlignment.Baseline || (textRun.Properties.TextEffects != null && textRun.Properties.TextEffects.Count != 0) ) { // fast path does not handle the following conditions // o non-default baseline alignment // o text drawing effect ( return null; } TextDecorationCollection textDecorations = textRun.Properties.TextDecorations; if ( textDecorations != null && textDecorations.Count != 0 && !textDecorations.ValueEquals(TextDecorations.Underline)) { // we only support a single underline return null; } settings.DigitState.SetTextRunProperties(textRun.Properties); if (settings.DigitState.RequiresNumberSubstitution) { // don't support number substitution in fast path return null; } bool canProcessTabsInSimpleShapingPath = CanProcessTabsInSimpleShapingPath( settings.Pap, settings.Formatter.TextFormattingMode ); if (charString[0] == TextStore.CharCarriageReturn) { // CR in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; if (charString.Length > 1 && charString[1] == TextStore.CharLineFeed) { runLength = 2; } // This path handles the case where the backing store breaks the text run in between // a Carriage Return and a Line Feed. So we fetch the next run to check whether the next // character is a line feed. else if (charString.Length == 1) { // Prefetch to check for line feed. TextRun newRun; int newRunLength; CharacterBufferRange newBufferRange = settings.FetchTextRun( cp + 1, cpFirst, out newRun, out newRunLength ); if (newBufferRange.Length > 0 && newBufferRange[0] == TextStore.CharLineFeed) { // Merge the 2 runs. int lengthOfRun = 2; char[] characterArray = new char[lengthOfRun]; characterArray[0] = TextStore.CharCarriageReturn; characterArray[1] = TextStore.CharLineFeed; TextRun mergedTextRun = new TextCharacters(characterArray, 0, lengthOfRun, textRun.Properties); return new SimpleRun(lengthOfRun, mergedTextRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } } return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (charString[0] == TextStore.CharLineFeed) { // LF in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (canProcessTabsInSimpleShapingPath && charString[0] == TextStore.CharTab) { return CreateSimpleRunForTab(settings, textRun, idealRunOffsetUnRounded); } // attempt to create a simple run for text run = CreateSimpleTextRun( charString, textRun, settings.Formatter, widthLeft, settings.Pap.EmergencyWrap, canProcessTabsInSimpleShapingPath ); if (run == null) { // fail to create simple text run, the run content is too complex return null; } // Check for underline condition if (textDecorations != null && textDecorations.Count == 1 ) { run.Underline = textDecorations[0]; } } else if (textRun is TextEndOfLine) { run = new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (textRun is TextHidden) { // hidden run run = new SimpleRun(runLength, textRun, Flags.Ghost, settings.Formatter); } return run; }
/// <summary> /// Constructing TextRunBounds /// </summary> internal TextRunBounds( Rect bounds, int cpFirst, int cpEnd, TextRun textRun ) { _cpFirst = cpFirst; _cch = cpEnd - cpFirst; _bounds = bounds; _textRun = textRun; }
/// <summary> /// Map TextRun type to known plsrun type /// </summary> internal static Plsrun GetRunType(TextRun textRun) { if (textRun is ITextSymbols || textRun is TextShapeableSymbols) return Plsrun.Text; if (textRun is TextEmbeddedObject) return Plsrun.InlineObject; if (textRun is TextEndOfParagraph) return Plsrun.ParaBreak; if (textRun is TextEndOfLine) return Plsrun.LineBreak; // Other text run type are all considered hidden by LS return Plsrun.Hidden; }
/// <summary> /// Constructing a textrun info /// </summary> /// <param name="charBufferRange">characte buffer range for the run</param> /// <param name="textRunLength">textrun length</param> /// <param name="offsetToFirstCp">character offset to run first cp</param> /// <param name="textRun">text run</param> /// <param name="lsRunType">the internal LS run type </param> /// <param name="charFlags">character attribute flags</param> /// <param name="digitCulture">digit culture for the run</param> /// <param name="contextualSubstitution">contextual number substitution for the run</param> /// <param name="symbolTypeface">if true, indicates a text run in a symbol (i.e., non-Unicode) font</param> /// <param name="modifierScope">The current TextModifier scope for this TextRunInfo</param> internal TextRunInfo( CharacterBufferRange charBufferRange, int textRunLength, int offsetToFirstCp, TextRun textRun, Plsrun lsRunType, ushort charFlags, CultureInfo digitCulture, bool contextualSubstitution, bool symbolTypeface, TextModifierScope modifierScope ) { _charBufferRange = charBufferRange; _textRunLength = textRunLength; _offsetToFirstCp = offsetToFirstCp; _textRun = textRun; _plsrun = lsRunType; _charFlags = charFlags; _digitCulture = digitCulture; _runFlags = 0; _modifierScope = modifierScope; if (contextualSubstitution) { _runFlags |= (ushort)RunFlags.ContextualSubstitution; } if (symbolTypeface) { _runFlags |= (ushort)RunFlags.IsSymbol; } }