// ------------------------------------------------------------------ // // TextSource Implementation // // ----------------------------------------------------------------- #region TextSource Implementation // ------------------------------------------------------------------ // Get a text run at specified text source position. // ------------------------------------------------------------------ public override TextRun GetTextRun(int dcp) { Debug.Assert(dcp >= 0, "Character index must be non-negative."); TextRun run; // There is only one run of text. if (dcp < _content.Length) { // LineLayout may ask for dcp != 0. This case may only happen during partial // validation of TextRunCache. // Example: // 1) TextRunCache and LineMetrics array were created during measure process. // 2) Before OnRender is called somebody invalidates render only property. // This invalidates TextRunCache. // 3) Before OnRender is called InputHitTest is invoked. Because LineMetrics // array is valid, we don't have to recreate all lines. There is only // need to recreate the N-th line (line that has been hit). // During line recreation LineLayout will not refetch all runs from the // beginning of TextBlock control - it will ask for the run at the beginning // of the current line. // For this reason set 'offsetToFirstChar' to 'dcp' value. run = new TextCharacters(_content, dcp, _content.Length - dcp, _textProps); } else { run = new TextEndOfParagraph(_syntheticCharacterLength); } return run; }
/// <summary> /// Return next TextRun at element edge start position /// </summary> /// <param name="position"> /// Current position in text array /// </param> protected TextRun HandleElementStartEdge(StaticTextPointer position) { Invariant.Assert(position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart, "TextPointer does not point to element start edge."); // TextRun run = null; TextElement element = (TextElement)position.GetAdjacentElement(LogicalDirection.Forward); Debug.Assert(element != null, "Cannot use ITextContainer that does not provide TextElement instances."); Invariant.Assert(!(element is Block), "We do not expect any Blocks inside Paragraphs"); // Treat figure and floaters as special hidden runs. if (element is Figure || element is Floater) { // Get the length of the element int cch = TextContainerHelper.GetElementLength(_paraClient.Paragraph.StructuralCache.TextContainer, element); // Create special hidden run. run = new FloatingRun(cch, element is Figure); if (element is Figure) { _hasFigures = true; } else { _hasFloaters = true; } } else if (element is LineBreak) { int cch = TextContainerHelper.GetElementLength(_paraClient.Paragraph.StructuralCache.TextContainer, element); run = new LineBreakRun(cch, PTS.FSFLRES.fsflrSoftBreak); } else if (element.IsEmpty) { // Empty TextElement should affect line metrics. // TextFormatter does not support this feature right now, so as workaround // TextRun with ZERO WIDTH SPACE is used. TextProperties textProps = new TextProperties(element, position, false /* inline objects */, true /* get background */); char[] textBuffer = new char[_elementEdgeCharacterLength * 2]; // Assert that _elementEdgeCharacterLength is 1 before we use hard-coded indices Invariant.Assert(_elementEdgeCharacterLength == 1, "Expected value of _elementEdgeCharacterLength is 1"); textBuffer[0] = (char)0x200B; textBuffer[1] = (char)0x200B; run = new TextCharacters(textBuffer, 0, textBuffer.Length, textProps); } else { Inline inline = (Inline) element; DependencyObject parent = inline.Parent; FlowDirection inlineFlowDirection = inline.FlowDirection; FlowDirection parentFlowDirection = inlineFlowDirection; TextDecorationCollection inlineTextDecorations = DynamicPropertyReader.GetTextDecorations(inline); if(parent != null) { parentFlowDirection = (FlowDirection)parent.GetValue(FrameworkElement.FlowDirectionProperty); } if (inlineFlowDirection != parentFlowDirection) { // Inline's flow direction is different from its parent. Need to create new TextSpanModifier with flow direction if (inlineTextDecorations == null || inlineTextDecorations.Count == 0) { run = new TextSpanModifier( _elementEdgeCharacterLength, null, null, inlineFlowDirection ); } else { run = new TextSpanModifier( _elementEdgeCharacterLength, inlineTextDecorations, inline.Foreground, inlineFlowDirection ); } } else { if (inlineTextDecorations == null || inlineTextDecorations.Count == 0) { run = new TextHidden(_elementEdgeCharacterLength); } else { run = new TextSpanModifier( _elementEdgeCharacterLength, inlineTextDecorations, inline.Foreground ); } } } return run; }
private TextRun MakeTextCharactors(int index, int textSourceCharacterIndex, int length) { TextCharacters textCharacters = new TextCharacters(this.Text, textSourceCharacterIndex, length, this.JapaneseTextRunProperties); this.UniscribeScriptShapeOpenType(this.Text, index, textSourceCharacterIndex, length); return textCharacters; }
public override TextRun GetTextRun(int textSourceCharacterIndex) { var index = textSourceCharacterIndex; if (runs.ContainsKey(index)) { var run = runs[index]; runs.Remove(index); return run; } if (index >= text.Length || text[index] == '\r' || text[index] == '\n') { if (index < text.Length && text[index] != '\r') return new TextCharacters(" ", null); else return new TextEndOfParagraph(1); } int defaultTextLength, tokenLength; TextTokenKind tokenKind; if (!tokens.Find(index, out defaultTextLength, out tokenKind, out tokenLength)) { Debug.Fail("Could not find token info"); return new TextCharacters(" ", null); } TextCharacters defaultRun = null, tokenRun = null; if (defaultTextLength != 0) { var defaultText = text.Substring(index, defaultTextLength); defaultRun = new TextCharacters(defaultText, new TextProps { background = (Brush)parent.GetValue(TextElement.BackgroundProperty), foreground = TextElement.GetForeground(parent), typeface = new Typeface( TextElement.GetFontFamily(parent), TextElement.GetFontStyle(parent), TextElement.GetFontWeight(parent), TextElement.GetFontStretch(parent) ), fontSize = TextElement.GetFontSize(parent), }); } index += defaultTextLength; if (tokenLength != 0) { var tc = GetColor(tokenKind); var tokenText = text.Substring(index, tokenLength); var textProps = new TextProps(); textProps.fontSize = TextElement.GetFontSize(parent); textProps.foreground = tc.Foreground ?? TextElement.GetForeground(parent); textProps.background = tc.Background ?? (Brush)parent.GetValue(TextElement.BackgroundProperty); textProps.typeface = new Typeface( TextElement.GetFontFamily(parent), tc.FontStyle ?? TextElement.GetFontStyle(parent), tc.FontWeight ?? TextElement.GetFontWeight(parent), TextElement.GetFontStretch(parent) ); tokenRun = new TextCharacters(tokenText, textProps); } Debug.Assert(defaultRun != null || tokenRun != null); if ((defaultRun != null) ^ (tokenRun != null)) return defaultRun ?? tokenRun; else { runs[index] = tokenRun; return defaultRun; } }
// ------------------------------------------------------------------ // Fetch the next run at element open edge position. // // position - current position in the text array // ------------------------------------------------------------------ private TextRun HandleElementStartEdge(StaticTextPointer position) { Debug.Assert(position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart, "TextPointer does not point to element start edge."); // TextRun run = null; TextElement element = (TextElement)position.GetAdjacentElement(LogicalDirection.Forward); Debug.Assert(element != null, "Cannot use ITextContainer that does not provide TextElement instances."); if (element is LineBreak) { run = new TextEndOfLine(_elementEdgeCharacterLength * 2); } else if (element.IsEmpty) { // Empty TextElement should affect line metrics. // TextFormatter does not support this feature right now, so as workaround // TextRun with ZERO WIDTH SPACE is used. TextRunProperties textProps = new TextProperties(element, position, false /* inline objects */, true /* get background */); char[] textBuffer = new char[_elementEdgeCharacterLength * 2]; textBuffer[0] = (char)0x200B; textBuffer[1] = (char)0x200B; run = new TextCharacters(textBuffer, 0, textBuffer.Length, textProps); } else { Inline inline = element as Inline; if (inline == null) { run = new TextHidden(_elementEdgeCharacterLength); } else { DependencyObject parent = inline.Parent; FlowDirection inlineFlowDirection = inline.FlowDirection; FlowDirection parentFlowDirection = inlineFlowDirection; if(parent != null) { parentFlowDirection = (FlowDirection)parent.GetValue(FrameworkElement.FlowDirectionProperty); } TextDecorationCollection inlineTextDecorations = DynamicPropertyReader.GetTextDecorations(inline); if (inlineFlowDirection != parentFlowDirection) { // Inline's flow direction is different from its parent. Need to create new TextSpanModifier with flow direction if (inlineTextDecorations == null || inlineTextDecorations.Count == 0) { run = new TextSpanModifier( _elementEdgeCharacterLength, null, null, inlineFlowDirection ); } else { run = new TextSpanModifier( _elementEdgeCharacterLength, inlineTextDecorations, inline.Foreground, inlineFlowDirection ); } } else { if (inlineTextDecorations == null || inlineTextDecorations.Count == 0) { run = new TextHidden(_elementEdgeCharacterLength); } else { run = new TextSpanModifier( _elementEdgeCharacterLength, inlineTextDecorations, inline.Foreground ); } } } } 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; }