Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / MS / Internal / PtsHost / Line.cs / 1 / Line.cs
//---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // // File: ContainerParagraph.cs // // Description: Text line formatter. // // History: // 05/05/2003 : grzegorz - moving from Avalon branch. // //--------------------------------------------------------------------------- #pragma warning disable 1634, 1691 // avoid generating warnings about unknown // message numbers and unknown pragmas for PRESharp contol using System; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Security; // SecurityCritical using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.TextFormatting; using MS.Internal.Text; using MS.Internal.Documents; using MS.Internal.PtsHost.UnsafeNativeMethods; namespace MS.Internal.PtsHost { ////// Text line formatter. /// ////// NOTE: All DCPs used during line formatting are related to cpPara. /// To get abosolute CP, add cpPara to a dcp value. /// internal sealed class Line : LineBase { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors ////// Constructor. /// /// /// TextFormatter host /// /// /// Owner of the line /// /// /// CP of the beginning of the text paragraph /// internal Line(TextFormatterHost host, TextParaClient paraClient, int cpPara) : base(paraClient) { _host = host; _cpPara = cpPara; _textAlignment = (TextAlignment)TextParagraph.Element.GetValue(Block.TextAlignmentProperty); _indent = 0.0; } ////// Free all resources associated with the line. Prepare it for reuse. /// public override void Dispose() { Debug.Assert(_line != null, "Line has been already disposed."); try { if (_line != null) { _line.Dispose(); } } finally { _line = null; _runs = null; _hasFigures = false; _hasFloaters = false; base.Dispose(); } } #endregion Constructors // ------------------------------------------------------------------ // // PTS Callbacks // // ----------------------------------------------------------------- #region PTS Callbacks ////// GetDvrSuppressibleBottomSpace /// /// /// OUT: empty space suppressible at the bottom /// internal void GetDvrSuppressibleBottomSpace( out int dvrSuppressible) { dvrSuppressible = Math.Max(0, TextDpi.ToTextDpi(_line.OverhangAfter)); } ////// GetDurFigureAnchor /// /// /// IN: FigureParagraph for which we require anchor dur /// /// /// IN: current direction /// /// /// OUT: distance from the beginning of the line to the anchor /// internal void GetDurFigureAnchor( FigureParagraph paraFigure, uint fswdir, out int dur) { int cpFigure = TextContainerHelper.GetCPFromElement(_paraClient.Paragraph.StructuralCache.TextContainer, paraFigure.Element, ElementEdge.BeforeStart); int dcpFigure = cpFigure - _cpPara; double distance = _line.GetDistanceFromCharacterHit(new CharacterHit(dcpFigure, 0)); dur = TextDpi.ToTextDpi(distance); } #endregion PTS Callbacks // ------------------------------------------------------------------ // // TextSource Implementation // // ------------------------------------------------------------------ #region TextSource Implementation ////// Get a text run at specified text source position and return it. /// /// /// dcp of position relative to start of line /// internal override TextRun GetTextRun(int dcp) { TextRun run = null; ITextContainer textContainer = _paraClient.Paragraph.StructuralCache.TextContainer; StaticTextPointer position = textContainer.CreateStaticPointerAtOffset(_cpPara + dcp); switch (position.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: run = HandleText(position); break; case TextPointerContext.ElementStart: run = HandleElementStartEdge(position); break; case TextPointerContext.ElementEnd: run = HandleElementEndEdge(position); break; case TextPointerContext.EmbeddedElement: run = HandleEmbeddedObject(dcp, position); break; case TextPointerContext.None: run = new ParagraphBreakRun(_syntheticCharacterLength, PTS.FSFLRES.fsflrEndOfParagraph); break; } Invariant.Assert(run != null, "TextRun has not been created."); Invariant.Assert(run.Length > 0, "TextRun has to have positive length."); return run; } ////// Get text immediately before specified text source position. Return CharacterBufferRange /// containing this text. /// /// /// dcp of position relative to start of line /// internal override TextSpanGetPrecedingText(int dcp) { // Parameter validation Invariant.Assert(dcp >= 0); int nonTextLength = 0; CharacterBufferRange precedingText = CharacterBufferRange.Empty; CultureInfo culture = null; if (dcp > 0) { // Create TextPointer at dcp, and pointer at paragraph start to compare ITextPointer startPosition = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, _cpPara, LogicalDirection.Forward); ITextPointer position = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, _cpPara + dcp, LogicalDirection.Forward); // Move backward until we find a position at the end of a text run, or reach start of TextContainer while (position.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text && position.CompareTo(startPosition) != 0) { position.MoveByOffset(-1); nonTextLength++; } // Return text in run. If it is at start of TextContainer this will return an empty string string precedingTextString = position.GetTextInRun(LogicalDirection.Backward); precedingText = new CharacterBufferRange(precedingTextString, 0, precedingTextString.Length); StaticTextPointer pointer = position.CreateStaticPointer(); DependencyObject element = (pointer.Parent != null) ? pointer.Parent : _paraClient.Paragraph.Element; culture = DynamicPropertyReader.GetCultureInfo(element); } return new TextSpan ( nonTextLength + precedingText.Length, new CultureSpecificCharacterBufferRange(culture, precedingText) ); } /// /// Get Text effect index from text source character index. Return int value of Text effect index. /// /// /// dcp of CharacterHit relative to start of line /// internal override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int dcp) { return _cpPara + dcp; } #endregion TextSource Implementation // ----------------------------------------------------------------- // // Internal Methods // // ------------------------------------------------------------------ #region Internal Methods ////// Create and format text line. /// /// /// Line formatting context. /// /// /// Character position where the line starts. /// /// /// Requested width of the line. /// /// /// Requested width of track. /// /// /// Line properties. /// /// /// Line break object. /// internal void Format(FormattingContext ctx, int dcp, int width, int trackWidth, TextParagraphProperties lineProps, TextLineBreak textLineBreak) { // Set formatting context _formattingContext = ctx; _dcp = dcp; _host.Context = this; _wrappingWidth = TextDpi.FromTextDpi(width); _trackWidth = TextDpi.FromTextDpi(trackWidth); _mirror = (lineProps.FlowDirection == FlowDirection.RightToLeft); _indent = lineProps.Indent; try { // Create line object if(ctx.LineFormatLengthTarget == -1) { _line = _host.TextFormatter.FormatLine(_host, dcp, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); } else { _line = _host.TextFormatter.RecreateLine(_host, dcp, ctx.LineFormatLengthTarget, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); } _runs = _line.GetTextRunSpans(); Invariant.Assert(_runs != null, "Cannot retrieve runs collection."); // Submit inline objects (only in measure mode) if (_formattingContext.MeasureMode) { ListinlineObjects = new List (1); int dcpRun = _dcp; // Enumerate through all runs in the current line and retrieve // all inline objects. // If there are any figures / floaters, store this information for later use. foreach (TextSpan textSpan in _runs) { TextRun run = (TextRun)textSpan.Value; if (run is InlineObjectRun) { inlineObjects.Add(new InlineObject(dcpRun, ((InlineObjectRun)run).UIElementIsland, (TextParagraph)_paraClient.Paragraph)); } else if (run is FloatingRun) { if (((FloatingRun)run).Figure) { _hasFigures = true; } else { _hasFloaters = true; } } // Do not use TextRun.Length, because it gives total length of the run. // So, if the run is broken between lines, it gives incorrect value. // Use length of the TextSpan instead, which gives the correct length here. dcpRun += textSpan.Length; } // Submit inline objects to the paragraph cache if (inlineObjects.Count == 0) { inlineObjects = null; } TextParagraph.SubmitInlineObjects(dcp, dcp + ActualLength, inlineObjects); } } finally { // Clear formatting context _host.Context = null; } } /// /// Measure child UIElement. /// /// /// Element whose size we are measuring /// ////// Size of the child UIElement /// internal Size MeasureChild(InlineObjectRun inlineObject) { // Measure inline object only during measure pass. Otherwise // use cached data. Size desiredSize; if (_formattingContext.MeasureMode) { Debug.Assert(!DoubleUtil.IsNaN(_trackWidth), "Track width must be set for measure pass."); // Always measure at infinity for bottomless, consistent constraint. double pageHeight = _paraClient.Paragraph.StructuralCache.CurrentFormatContext.DocumentPageSize.Height; if (!_paraClient.Paragraph.StructuralCache.CurrentFormatContext.FinitePage) { pageHeight = Double.PositiveInfinity; } desiredSize = inlineObject.UIElementIsland.DoLayout(new Size(_trackWidth, pageHeight), true, true); } else { desiredSize = inlineObject.UIElementIsland.Root.DesiredSize; } return desiredSize; } ////// Create and return visual node for the line. /// internal ContainerVisual CreateVisual() { LineVisual visual = new LineVisual(); // Set up the text source for rendering callback _host.Context = this; try { // Handle text trimming. IList> runs = _runs; System.Windows.Media.TextFormatting.TextLine line = _line; if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); runs = line.GetTextRunSpans(); } // Add visuals for all embedded elements. if (HasInlineObjects()) { VisualCollection visualChildren = visual.Children; // Get flow direction of the paragraph element. DependencyObject paragraphElement = _paraClient.Paragraph.Element; FlowDirection paragraphFlowDirection = (FlowDirection)paragraphElement.GetValue(FrameworkElement.FlowDirectionProperty); // Before text rendering, add all visuals for inline objects. int dcpRun = _dcp; // Enumerate through all runs in the current line and connect visuals for all inline objects. foreach (TextSpan textSpan in runs) { TextRun run = (TextRun)textSpan.Value; if (run is InlineObjectRun) { InlineObjectRun inlineObject = (InlineObjectRun)run; FlowDirection flowDirection; Rect rect = GetBoundsFromPosition(dcpRun, run.Length, out flowDirection); Debug.Assert(DoubleUtil.GreaterThanOrClose(rect.Width, 0), "Negative inline object's width."); // Disconnect visual from its old parent, if necessary. Visual currentParent = VisualTreeHelper.GetParent(inlineObject.UIElementIsland) as Visual; if (currentParent != null) { ContainerVisual parent = currentParent as ContainerVisual; Invariant.Assert(parent != null, "Parent should always derives from ContainerVisual."); parent.Children.Remove(inlineObject.UIElementIsland); } if (!line.HasCollapsed || ((rect.Left + inlineObject.UIElementIsland.Root.DesiredSize.Width) < line.Width)) { // Check parent's FlowDirection to determine if mirroring is needed if (inlineObject.UIElementIsland.Root is FrameworkElement) { DependencyObject parent = ((FrameworkElement)inlineObject.UIElementIsland.Root).Parent; FlowDirection parentFlowDirection = (FlowDirection)parent.GetValue(FrameworkElement.FlowDirectionProperty); PtsHelper.UpdateMirroringTransform(paragraphFlowDirection, parentFlowDirection, inlineObject.UIElementIsland, rect.Width); } visualChildren.Add(inlineObject.UIElementIsland); inlineObject.UIElementIsland.Offset = new Vector(rect.Left, rect.Top); } } // Do not use TextRun.Length, because it gives total length of the run. // So, if the run is broken between lines, it gives incorrect value. // Use length of the TextSpan instead, which gives the correct length here. dcpRun += textSpan.Length; } } // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); DrawingContext ctx = visual.Open(); line.Draw(ctx, new Point(delta, 0), (_mirror ? InvertAxes.Horizontal : InvertAxes.None)); ctx.Close(); visual.WidthIncludingTrailingWhitespace = line.WidthIncludingTrailingWhitespace - _indent; } finally { _host.Context = null; // clear the context } return visual; } /// /// Return bounds of an object/character at specified text position. /// /// /// Position of the object/character /// /// /// Flow direction of the object/character /// internal Rect GetBoundsFromTextPosition(int textPosition, out FlowDirection flowDirection) { return GetBoundsFromPosition(textPosition, 1, out flowDirection); } ////// Returns an ArrayList of rectangles (Rect) that form the bounds of the region specified between /// the start and end points /// /// /// int offset indicating the starting point of the region for which bounds are required /// /// Length in characters of the region for which bounds are required /// /// /// Offset of line in x direction, to be added to line bounds to get actual rectangle for line /// /// /// Offset of line in y direction, to be added to line bounds to get actual rectangle for line /// ////// This function calls GetTextBounds for the line, and then checks if there are text run bounds. If they exist, /// it uses those as the bounding rectangles. If not, it returns the rectangle for the first (and only) element /// of the text bounds. /// internal ListGetRangeBounds(int cp, int cch, double xOffset, double yOffset) { List rectangles = new List (); // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); double newUOffset = xOffset + delta; IList textBounds; if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { // Verify that offset shift is 0 for this case. We should never shift offsets when ellipses are // rendered. Invariant.Assert(DoubleUtil.AreClose(delta, 0)); System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); textBounds = line.GetTextBounds(cp, cch); } else { textBounds = _line.GetTextBounds(cp, cch); } Invariant.Assert(textBounds.Count > 0); for (int boundIndex = 0; boundIndex < textBounds.Count; boundIndex++) { Rect rect = textBounds[boundIndex].Rectangle; rect.X += newUOffset; rect.Y += yOffset; rectangles.Add(rect); } return rectangles; } /// /// Passes line break object out from underlying line object /// internal TextLineBreak GetTextLineBreak() { if(_line == null) { return null; } return _line.GetTextLineBreak(); } ////// Return text position index from the given distance. /// /// /// Distance relative to the beginning of the line. /// internal CharacterHit GetTextPositionFromDistance(int urDistance) { // Calculate shift in line offset to render trailing spaces or avoid clipping text int delta = CalculateUOffsetShift(); if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(delta == 0); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); return line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urDistance)); } return _line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urDistance - delta)); } ////// Hit tests to the correct ContentElement within the line. /// /// /// Offset within the line. /// ////// ContentElement which has been hit. /// internal IInputElement InputHitTest(int urOffset) { DependencyObject element = null; TextPointer position; TextPointerContext type = TextPointerContext.None; CharacterHit charIndex; int cp, delta; // Calculate shift in line offset to render trailing spaces or avoid clipping text delta = CalculateUOffsetShift(); if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { // We should not shift offset in this case Invariant.Assert(delta == 0); System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); // Get TextPointer from specified distance. charIndex = line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urOffset)); } else { // Get TextPointer from specified distance. charIndex = _line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urOffset - delta)); } cp = _paraClient.Paragraph.ParagraphStartCharacterPosition + charIndex.FirstCharacterIndex + charIndex.TrailingLength; position = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, cp, LogicalDirection.Forward) as TextPointer; if (position != null) { // If start of character, look forward. Otherwise, look backward. type = position.GetPointerContext((charIndex.TrailingLength == 0) ? LogicalDirection.Forward : LogicalDirection.Backward); // Get element only for Text & Start/End element, for all other positions // return null (it means that the line owner has been hit). if (type == TextPointerContext.Text || type == TextPointerContext.ElementEnd) { element = position.Parent; } else if (type == TextPointerContext.ElementStart) { element = position.GetAdjacentElementFromOuterPosition(LogicalDirection.Forward); } } return element as IInputElement; } ////// Get length of content hidden by ellipses. Return integer length of this content. /// internal int GetEllipsesLength() { // There are no ellipses, if: // * there is no overflow in the line // * text trimming is turned off if (!_line.HasOverflowed) { return 0; } if (TextParagraph.Properties.TextTrimming == TextTrimming.None) { return 0; } // Create collapsed text line to get length of collapsed content. System.Windows.Media.TextFormatting.TextLine collapsedLine = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(collapsedLine.HasCollapsed, "Line has not been collapsed"); IListcollapsedRanges = collapsedLine.GetTextCollapsedRanges(); if (collapsedRanges != null) { Invariant.Assert(collapsedRanges.Count == 1, "Multiple collapsed ranges are not supported."); TextCollapsedRange collapsedRange = collapsedRanges[0]; return collapsedRange.Length; } return 0; } /// /// Retrieves collection of GlyphRuns from a range of text. /// /// /// Glyph runs. /// /// /// Start dcp of range /// /// /// End dcp of range. /// internal void GetGlyphRuns(System.Collections.Generic.ListglyphRuns, int dcpStart, int dcpEnd) { // NOTE: Following logic is only temporary workaround for lack // of appropriate API that should be exposed by TextLine. int dcp = dcpStart - _dcp; int cch = dcpEnd - dcpStart; Debug.Assert(dcp >= 0 && (dcp + cch <= _line.Length)); IList > spans = _line.GetTextRunSpans(); DrawingGroup drawing = new DrawingGroup(); DrawingContext ctx = drawing.Open(); // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); _line.Draw(ctx, new Point(delta, 0), InvertAxes.None); ctx.Close(); // Copy glyph runs into separate array (for backward navigation). // And count number of chracters in the glyph runs collection. int cchGlyphRuns = 0; ArrayList glyphRunsCollection = new ArrayList(4); AddGlyphRunRecursive(drawing, glyphRunsCollection, ref cchGlyphRuns); Debug.Assert(cchGlyphRuns > 0 && glyphRunsCollection.Count > 0); // Count number of characters in text runs. int cchTextSpans = 0; foreach (TextSpan textSpan in spans) { if (textSpan.Value is TextCharacters) { cchTextSpans += textSpan.Length; } } // If number of characters in glyph runs is greater than number of characters // in text runs, it means that there is bullet at the beginning of the line // or hyphen at the end of the line. // For now hyphen case is ignored. // Remove those glyph runs from our colleciton. while (cchGlyphRuns > cchTextSpans) { GlyphRun glyphRun = (GlyphRun)glyphRunsCollection[0]; cchGlyphRuns -= (glyphRun.Characters == null ? 0 : glyphRun.Characters.Count); glyphRunsCollection.RemoveAt(0); } int curDcp = 0; int runIndex = 0; foreach (TextSpan span in spans) { if (span.Value is TextCharacters) { int cchRunsInSpan = 0; while (cchRunsInSpan < span.Length) { Invariant.Assert(runIndex < glyphRunsCollection.Count); GlyphRun run = (GlyphRun)glyphRunsCollection[runIndex]; int characterCount = (run.Characters == null ? 0 : run.Characters.Count); if ((dcp < curDcp + characterCount) && (dcp + cch > curDcp)) { glyphRuns.Add(run); } cchRunsInSpan += characterCount; ++runIndex; } Invariant.Assert(cchRunsInSpan == span.Length); // No need to continue, if dcpEnd has been reached. if (dcp + cch <= curDcp + span.Length) break; } curDcp += span.Length; } } /// /// Return text position for next caret position /// /// /// CharacterHit for current position /// internal CharacterHit GetNextCaretCharacterHit(CharacterHit index) { return _line.GetNextCaretCharacterHit(index); } ////// Return text position for previous caret position /// /// /// CharacterHit for current position /// internal CharacterHit GetPreviousCaretCharacterHit(CharacterHit index) { return _line.GetPreviousCaretCharacterHit(index); } ////// Return text position for backspace caret position /// /// /// CharacterHit for current position /// internal CharacterHit GetBackspaceCaretCharacterHit(CharacterHit index) { return _line.GetBackspaceCaretCharacterHit(index); } ////// Returns true of char hit is at caret unit boundary. /// /// /// CharacterHit to be tested. /// internal bool IsAtCaretCharacterHit(CharacterHit charHit) { return _line.IsAtCaretCharacterHit(charHit, _dcp); } #endregion Internal Methods //------------------------------------------------------------------- // // Internal Properties // //------------------------------------------------------------------- #region Internal Properties ////// Distance from the beginning of paragraph edge to the line edge. /// internal int Start { get { return TextDpi.ToTextDpi(_line.Start) + TextDpi.ToTextDpi(_indent) + CalculateUOffsetShift(); } } ////// Calculated width of the line. /// internal int Width { get { int width; if (IsWidthAdjusted) { width = TextDpi.ToTextDpi(_line.WidthIncludingTrailingWhitespace) - TextDpi.ToTextDpi(_indent); } else { width = TextDpi.ToTextDpi(_line.Width) - TextDpi.ToTextDpi(_indent); } Invariant.Assert(width >= 0, "Line width cannot be negative"); return width; } } ////// Height of the line; line advance distance. /// internal int Height { get { return TextDpi.ToTextDpi(_line.Height); } } ////// Baseline offset from the top of the line. /// internal int Baseline { get { return TextDpi.ToTextDpi(_line.Baseline); } } ////// True if last line of paragraph /// internal bool EndOfParagraph { get { // If there are no Newline characters, it is not the end of paragraph. if (_line.NewlineLength == 0) { return false; } // Since there are Newline characters in the line, do more expensive and // accurate check. return (((TextSpan)_runs[_runs.Count-1]).Value is ParagraphBreakRun); } } /// /// Length of the line including any synthetic characters. /// This length is PTS frendly. PTS does not like 0 length lines. /// internal int SafeLength { get { return _line.Length; } } ////// Length of the line excluding any synthetic characters. /// internal int ActualLength { get { return _line.Length - (EndOfParagraph ? _syntheticCharacterLength : 0); } } ////// Length of the line excluding any synthetic characters and line breaks. /// internal int ContentLength { get { return _line.Length - _line.NewlineLength; } } ////// Number of characters after the end of the line which may affect /// line wrapping. /// internal int DependantLength { get { return _line.DependentLength; } } ////// Was line truncated (forced broken)? /// internal bool IsTruncated { get { return _line.IsTruncated; } } ////// Formatting result of the line. /// internal PTS.FSFLRES FormattingResult { get { PTS.FSFLRES formatResult = PTS.FSFLRES.fsflrOutOfSpace; // If there are no Newline characters, we run out of space. if (_line.NewlineLength == 0) { return formatResult; } // Since there are Newline characters in the line, do more expensive and // accurate check. TextRun run = ((TextSpan)_runs[_runs.Count - 1]).Value as TextRun; if (run is ParagraphBreakRun) { formatResult = ((ParagraphBreakRun)run).BreakReason; } else if (run is LineBreakRun) { formatResult = ((LineBreakRun)run).BreakReason; } return formatResult; } } #endregion Internal Properties //------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------- #region Private Methods /// /// Returns true if there are any inline objects, false otherwise. /// private bool HasInlineObjects() { bool hasInlineObjects = false; foreach (TextSpantextSpan in _runs) { if (textSpan.Value is InlineObjectRun) { hasInlineObjects = true; break; } } return hasInlineObjects; } /// /// Returns bounds of an object/character at specified text index. /// /// /// Character index of an object/character /// /// /// Number of positions occupied by object/character /// /// /// Flow direction of object/character /// ///private Rect GetBoundsFromPosition(int cp, int cch, out FlowDirection flowDirection) { Rect rect; // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); IList textBounds; if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { // We should not shift offset in this case Invariant.Assert(DoubleUtil.AreClose(delta, 0)); System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); textBounds = line.GetTextBounds(cp, cch); } else { textBounds = _line.GetTextBounds(cp, cch); } Invariant.Assert(textBounds != null && textBounds.Count == 1, "Expecting exactly one TextBounds for a single text position."); IList runBounds = textBounds[0].TextRunBounds; if (runBounds != null) { Debug.Assert(runBounds.Count == 1, "Expecting exactly one TextRunBounds for a single text position."); rect = runBounds[0].Rectangle; } else { rect = textBounds[0].Rectangle; } flowDirection = textBounds[0].FlowDirection; rect.X = rect.X + delta; return rect; } /// /// Returns Line collapsing properties /// /// /// Wrapping width for collapsed line. /// /// /// Paragraph properties /// private TextCollapsingProperties GetCollapsingProps(double wrappingWidth, LineProperties paraProperties) { Invariant.Assert(paraProperties.TextTrimming != TextTrimming.None, "Text trimming must be enabled."); TextCollapsingProperties collapsingProps; if (paraProperties.TextTrimming == TextTrimming.CharacterEllipsis) { collapsingProps = new TextTrailingCharacterEllipsis(wrappingWidth, paraProperties.DefaultTextRunProperties); } else { collapsingProps = new TextTrailingWordEllipsis(wrappingWidth, paraProperties.DefaultTextRunProperties); } return collapsingProps; } ////// Perform depth-first search on a drawing tree to add all the glyph /// runs to the collection /// /// /// Drawing on which we perform DFS /// /// /// Glyph run collection. /// /// /// Character length of glyph run collection /// private void AddGlyphRunRecursive( Drawing drawing, IList glyphRunsCollection, ref int cchGlyphRuns) { DrawingGroup group = drawing as DrawingGroup; if (group != null) { foreach (Drawing child in group.Children) { AddGlyphRunRecursive(child, glyphRunsCollection, ref cchGlyphRuns); } } else { GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing; if (glyphRunDrawing != null) { // Add a glyph run GlyphRun glyphRun = glyphRunDrawing.GlyphRun; if (glyphRun != null) { cchGlyphRuns += (glyphRun.Characters == null ? 0 : glyphRun.Characters.Count); glyphRunsCollection.Add(glyphRun); } } } } ////// Returns amount of shift for X-offset to render trailing spaces /// internal int CalculateUOffsetShift() { int width; int trailingSpacesDelta = 0; // Calculate amount by which to to move line back if trailing spaces are rendered if (IsUOffsetAdjusted) { width = TextDpi.ToTextDpi(_line.WidthIncludingTrailingWhitespace); trailingSpacesDelta = TextDpi.ToTextDpi(_line.Width) - width; Invariant.Assert(trailingSpacesDelta <= 0); } else { width = TextDpi.ToTextDpi(_line.Width); trailingSpacesDelta = 0; } // Calculate amount to shift line forward in case we are clipping the front of the line. // If line is showing ellipsis do not perform this check since we should not be clipping the front // of the line anyway int widthDelta = 0; if ((_textAlignment == TextAlignment.Center || _textAlignment == TextAlignment.Right) && !ShowEllipses) { if (width > TextDpi.ToTextDpi(_wrappingWidth)) { widthDelta = width - TextDpi.ToTextDpi(_wrappingWidth); } else { widthDelta = 0; } } int totalShift; if (_textAlignment == TextAlignment.Center) { // Divide shift by two to center line totalShift = (int)((widthDelta + trailingSpacesDelta) / 2); } else { totalShift = widthDelta + trailingSpacesDelta; } return totalShift; } #endregion Private methods //------------------------------------------------------------------- // // Private Properties // //-------------------------------------------------------------------- #region Private Properties ////// True if line ends in hard break /// private bool HasLineBreak { get { return (_line.NewlineLength > 0); } } ////// True if line's X-offset needs adjustment to render trailing spaces /// private bool IsUOffsetAdjusted { get { return ((_textAlignment == TextAlignment.Right || _textAlignment == TextAlignment.Center) && IsWidthAdjusted); } } ////// True if line's width is adjusted to include trailing spaces. For right and center alignment we need to /// adjust line offset as well, but for left alignment we need to only make a width asjustment /// private bool IsWidthAdjusted { get { bool adjusted = false; // Trailing spaces rendered only around hard breaks if (HasLineBreak || EndOfParagraph) { // Lines with ellipsis are not shifted because ellipsis would not appear after trailing spaces if (!ShowEllipses) { adjusted = true; } } return adjusted; } } ////// True if eliipsis is displayed in the line /// private bool ShowEllipses { get { if (TextParagraph.Properties.TextTrimming == TextTrimming.None) { return false; } if (_line.HasOverflowed) { return true; } return false; } } ////// Text Paragraph this line is formatted for /// private TextParagraph TextParagraph { get { return _paraClient.Paragraph as TextParagraph; } } #endregion Private Properties //-------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields ////// TextFormatter host /// private readonly TextFormatterHost _host; ////// Character position at the beginning of text paragraph. All DCPs /// of the line are relative to this value. /// private readonly int _cpPara; ////// Line formatting context. Valid only during formatting. /// private FormattingContext _formattingContext; ////// Text line objects /// private System.Windows.Media.TextFormatting.TextLine _line; ////// Cached run list. This list needs to be in [....] with _line object. /// Every time the line is recreated, this list needs to be updated. /// private IList> _runs; /// /// Character position at the beginning of the line. /// private int _dcp; ////// Line wrapping width /// private double _wrappingWidth; ////// Track width (line width ignoring floats) /// private double _trackWidth = Double.NaN; ////// Is text mirrored? /// private bool _mirror; ////// Text indent. 0 for all lines except the first line, which maybe have non-zero indent. /// private double _indent; ////// TextAlignment of owner /// private TextAlignment _textAlignment; #endregion Private Fields // ------------------------------------------------------------------ // // FormattingContext Class // // ----------------------------------------------------------------- #region FormattingContext Class ////// Text line formatting context /// internal class FormattingContext { internal FormattingContext(bool measureMode, bool clearOnLeft, bool clearOnRight, TextRunCache textRunCache) { MeasureMode = measureMode; ClearOnLeft = clearOnLeft; ClearOnRight = clearOnRight; TextRunCache = textRunCache; LineFormatLengthTarget = -1; } internal TextRunCache TextRunCache; internal bool MeasureMode; internal bool ClearOnLeft; internal bool ClearOnRight; internal int LineFormatLengthTarget; } #endregion FormattingContext Class } } #pragma warning enable 1634, 1691 // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // // File: ContainerParagraph.cs // // Description: Text line formatter. // // History: // 05/05/2003 : grzegorz - moving from Avalon branch. // //--------------------------------------------------------------------------- #pragma warning disable 1634, 1691 // avoid generating warnings about unknown // message numbers and unknown pragmas for PRESharp contol using System; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Security; // SecurityCritical using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.TextFormatting; using MS.Internal.Text; using MS.Internal.Documents; using MS.Internal.PtsHost.UnsafeNativeMethods; namespace MS.Internal.PtsHost { ////// Text line formatter. /// ////// NOTE: All DCPs used during line formatting are related to cpPara. /// To get abosolute CP, add cpPara to a dcp value. /// internal sealed class Line : LineBase { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors ////// Constructor. /// /// /// TextFormatter host /// /// /// Owner of the line /// /// /// CP of the beginning of the text paragraph /// internal Line(TextFormatterHost host, TextParaClient paraClient, int cpPara) : base(paraClient) { _host = host; _cpPara = cpPara; _textAlignment = (TextAlignment)TextParagraph.Element.GetValue(Block.TextAlignmentProperty); _indent = 0.0; } ////// Free all resources associated with the line. Prepare it for reuse. /// public override void Dispose() { Debug.Assert(_line != null, "Line has been already disposed."); try { if (_line != null) { _line.Dispose(); } } finally { _line = null; _runs = null; _hasFigures = false; _hasFloaters = false; base.Dispose(); } } #endregion Constructors // ------------------------------------------------------------------ // // PTS Callbacks // // ----------------------------------------------------------------- #region PTS Callbacks ////// GetDvrSuppressibleBottomSpace /// /// /// OUT: empty space suppressible at the bottom /// internal void GetDvrSuppressibleBottomSpace( out int dvrSuppressible) { dvrSuppressible = Math.Max(0, TextDpi.ToTextDpi(_line.OverhangAfter)); } ////// GetDurFigureAnchor /// /// /// IN: FigureParagraph for which we require anchor dur /// /// /// IN: current direction /// /// /// OUT: distance from the beginning of the line to the anchor /// internal void GetDurFigureAnchor( FigureParagraph paraFigure, uint fswdir, out int dur) { int cpFigure = TextContainerHelper.GetCPFromElement(_paraClient.Paragraph.StructuralCache.TextContainer, paraFigure.Element, ElementEdge.BeforeStart); int dcpFigure = cpFigure - _cpPara; double distance = _line.GetDistanceFromCharacterHit(new CharacterHit(dcpFigure, 0)); dur = TextDpi.ToTextDpi(distance); } #endregion PTS Callbacks // ------------------------------------------------------------------ // // TextSource Implementation // // ------------------------------------------------------------------ #region TextSource Implementation ////// Get a text run at specified text source position and return it. /// /// /// dcp of position relative to start of line /// internal override TextRun GetTextRun(int dcp) { TextRun run = null; ITextContainer textContainer = _paraClient.Paragraph.StructuralCache.TextContainer; StaticTextPointer position = textContainer.CreateStaticPointerAtOffset(_cpPara + dcp); switch (position.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.Text: run = HandleText(position); break; case TextPointerContext.ElementStart: run = HandleElementStartEdge(position); break; case TextPointerContext.ElementEnd: run = HandleElementEndEdge(position); break; case TextPointerContext.EmbeddedElement: run = HandleEmbeddedObject(dcp, position); break; case TextPointerContext.None: run = new ParagraphBreakRun(_syntheticCharacterLength, PTS.FSFLRES.fsflrEndOfParagraph); break; } Invariant.Assert(run != null, "TextRun has not been created."); Invariant.Assert(run.Length > 0, "TextRun has to have positive length."); return run; } ////// Get text immediately before specified text source position. Return CharacterBufferRange /// containing this text. /// /// /// dcp of position relative to start of line /// internal override TextSpanGetPrecedingText(int dcp) { // Parameter validation Invariant.Assert(dcp >= 0); int nonTextLength = 0; CharacterBufferRange precedingText = CharacterBufferRange.Empty; CultureInfo culture = null; if (dcp > 0) { // Create TextPointer at dcp, and pointer at paragraph start to compare ITextPointer startPosition = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, _cpPara, LogicalDirection.Forward); ITextPointer position = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, _cpPara + dcp, LogicalDirection.Forward); // Move backward until we find a position at the end of a text run, or reach start of TextContainer while (position.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text && position.CompareTo(startPosition) != 0) { position.MoveByOffset(-1); nonTextLength++; } // Return text in run. If it is at start of TextContainer this will return an empty string string precedingTextString = position.GetTextInRun(LogicalDirection.Backward); precedingText = new CharacterBufferRange(precedingTextString, 0, precedingTextString.Length); StaticTextPointer pointer = position.CreateStaticPointer(); DependencyObject element = (pointer.Parent != null) ? pointer.Parent : _paraClient.Paragraph.Element; culture = DynamicPropertyReader.GetCultureInfo(element); } return new TextSpan ( nonTextLength + precedingText.Length, new CultureSpecificCharacterBufferRange(culture, precedingText) ); } /// /// Get Text effect index from text source character index. Return int value of Text effect index. /// /// /// dcp of CharacterHit relative to start of line /// internal override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int dcp) { return _cpPara + dcp; } #endregion TextSource Implementation // ----------------------------------------------------------------- // // Internal Methods // // ------------------------------------------------------------------ #region Internal Methods ////// Create and format text line. /// /// /// Line formatting context. /// /// /// Character position where the line starts. /// /// /// Requested width of the line. /// /// /// Requested width of track. /// /// /// Line properties. /// /// /// Line break object. /// internal void Format(FormattingContext ctx, int dcp, int width, int trackWidth, TextParagraphProperties lineProps, TextLineBreak textLineBreak) { // Set formatting context _formattingContext = ctx; _dcp = dcp; _host.Context = this; _wrappingWidth = TextDpi.FromTextDpi(width); _trackWidth = TextDpi.FromTextDpi(trackWidth); _mirror = (lineProps.FlowDirection == FlowDirection.RightToLeft); _indent = lineProps.Indent; try { // Create line object if(ctx.LineFormatLengthTarget == -1) { _line = _host.TextFormatter.FormatLine(_host, dcp, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); } else { _line = _host.TextFormatter.RecreateLine(_host, dcp, ctx.LineFormatLengthTarget, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); } _runs = _line.GetTextRunSpans(); Invariant.Assert(_runs != null, "Cannot retrieve runs collection."); // Submit inline objects (only in measure mode) if (_formattingContext.MeasureMode) { ListinlineObjects = new List (1); int dcpRun = _dcp; // Enumerate through all runs in the current line and retrieve // all inline objects. // If there are any figures / floaters, store this information for later use. foreach (TextSpan textSpan in _runs) { TextRun run = (TextRun)textSpan.Value; if (run is InlineObjectRun) { inlineObjects.Add(new InlineObject(dcpRun, ((InlineObjectRun)run).UIElementIsland, (TextParagraph)_paraClient.Paragraph)); } else if (run is FloatingRun) { if (((FloatingRun)run).Figure) { _hasFigures = true; } else { _hasFloaters = true; } } // Do not use TextRun.Length, because it gives total length of the run. // So, if the run is broken between lines, it gives incorrect value. // Use length of the TextSpan instead, which gives the correct length here. dcpRun += textSpan.Length; } // Submit inline objects to the paragraph cache if (inlineObjects.Count == 0) { inlineObjects = null; } TextParagraph.SubmitInlineObjects(dcp, dcp + ActualLength, inlineObjects); } } finally { // Clear formatting context _host.Context = null; } } /// /// Measure child UIElement. /// /// /// Element whose size we are measuring /// ////// Size of the child UIElement /// internal Size MeasureChild(InlineObjectRun inlineObject) { // Measure inline object only during measure pass. Otherwise // use cached data. Size desiredSize; if (_formattingContext.MeasureMode) { Debug.Assert(!DoubleUtil.IsNaN(_trackWidth), "Track width must be set for measure pass."); // Always measure at infinity for bottomless, consistent constraint. double pageHeight = _paraClient.Paragraph.StructuralCache.CurrentFormatContext.DocumentPageSize.Height; if (!_paraClient.Paragraph.StructuralCache.CurrentFormatContext.FinitePage) { pageHeight = Double.PositiveInfinity; } desiredSize = inlineObject.UIElementIsland.DoLayout(new Size(_trackWidth, pageHeight), true, true); } else { desiredSize = inlineObject.UIElementIsland.Root.DesiredSize; } return desiredSize; } ////// Create and return visual node for the line. /// internal ContainerVisual CreateVisual() { LineVisual visual = new LineVisual(); // Set up the text source for rendering callback _host.Context = this; try { // Handle text trimming. IList> runs = _runs; System.Windows.Media.TextFormatting.TextLine line = _line; if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); runs = line.GetTextRunSpans(); } // Add visuals for all embedded elements. if (HasInlineObjects()) { VisualCollection visualChildren = visual.Children; // Get flow direction of the paragraph element. DependencyObject paragraphElement = _paraClient.Paragraph.Element; FlowDirection paragraphFlowDirection = (FlowDirection)paragraphElement.GetValue(FrameworkElement.FlowDirectionProperty); // Before text rendering, add all visuals for inline objects. int dcpRun = _dcp; // Enumerate through all runs in the current line and connect visuals for all inline objects. foreach (TextSpan textSpan in runs) { TextRun run = (TextRun)textSpan.Value; if (run is InlineObjectRun) { InlineObjectRun inlineObject = (InlineObjectRun)run; FlowDirection flowDirection; Rect rect = GetBoundsFromPosition(dcpRun, run.Length, out flowDirection); Debug.Assert(DoubleUtil.GreaterThanOrClose(rect.Width, 0), "Negative inline object's width."); // Disconnect visual from its old parent, if necessary. Visual currentParent = VisualTreeHelper.GetParent(inlineObject.UIElementIsland) as Visual; if (currentParent != null) { ContainerVisual parent = currentParent as ContainerVisual; Invariant.Assert(parent != null, "Parent should always derives from ContainerVisual."); parent.Children.Remove(inlineObject.UIElementIsland); } if (!line.HasCollapsed || ((rect.Left + inlineObject.UIElementIsland.Root.DesiredSize.Width) < line.Width)) { // Check parent's FlowDirection to determine if mirroring is needed if (inlineObject.UIElementIsland.Root is FrameworkElement) { DependencyObject parent = ((FrameworkElement)inlineObject.UIElementIsland.Root).Parent; FlowDirection parentFlowDirection = (FlowDirection)parent.GetValue(FrameworkElement.FlowDirectionProperty); PtsHelper.UpdateMirroringTransform(paragraphFlowDirection, parentFlowDirection, inlineObject.UIElementIsland, rect.Width); } visualChildren.Add(inlineObject.UIElementIsland); inlineObject.UIElementIsland.Offset = new Vector(rect.Left, rect.Top); } } // Do not use TextRun.Length, because it gives total length of the run. // So, if the run is broken between lines, it gives incorrect value. // Use length of the TextSpan instead, which gives the correct length here. dcpRun += textSpan.Length; } } // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); DrawingContext ctx = visual.Open(); line.Draw(ctx, new Point(delta, 0), (_mirror ? InvertAxes.Horizontal : InvertAxes.None)); ctx.Close(); visual.WidthIncludingTrailingWhitespace = line.WidthIncludingTrailingWhitespace - _indent; } finally { _host.Context = null; // clear the context } return visual; } /// /// Return bounds of an object/character at specified text position. /// /// /// Position of the object/character /// /// /// Flow direction of the object/character /// internal Rect GetBoundsFromTextPosition(int textPosition, out FlowDirection flowDirection) { return GetBoundsFromPosition(textPosition, 1, out flowDirection); } ////// Returns an ArrayList of rectangles (Rect) that form the bounds of the region specified between /// the start and end points /// /// /// int offset indicating the starting point of the region for which bounds are required /// /// Length in characters of the region for which bounds are required /// /// /// Offset of line in x direction, to be added to line bounds to get actual rectangle for line /// /// /// Offset of line in y direction, to be added to line bounds to get actual rectangle for line /// ////// This function calls GetTextBounds for the line, and then checks if there are text run bounds. If they exist, /// it uses those as the bounding rectangles. If not, it returns the rectangle for the first (and only) element /// of the text bounds. /// internal ListGetRangeBounds(int cp, int cch, double xOffset, double yOffset) { List rectangles = new List (); // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); double newUOffset = xOffset + delta; IList textBounds; if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { // Verify that offset shift is 0 for this case. We should never shift offsets when ellipses are // rendered. Invariant.Assert(DoubleUtil.AreClose(delta, 0)); System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); textBounds = line.GetTextBounds(cp, cch); } else { textBounds = _line.GetTextBounds(cp, cch); } Invariant.Assert(textBounds.Count > 0); for (int boundIndex = 0; boundIndex < textBounds.Count; boundIndex++) { Rect rect = textBounds[boundIndex].Rectangle; rect.X += newUOffset; rect.Y += yOffset; rectangles.Add(rect); } return rectangles; } /// /// Passes line break object out from underlying line object /// internal TextLineBreak GetTextLineBreak() { if(_line == null) { return null; } return _line.GetTextLineBreak(); } ////// Return text position index from the given distance. /// /// /// Distance relative to the beginning of the line. /// internal CharacterHit GetTextPositionFromDistance(int urDistance) { // Calculate shift in line offset to render trailing spaces or avoid clipping text int delta = CalculateUOffsetShift(); if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(delta == 0); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); return line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urDistance)); } return _line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urDistance - delta)); } ////// Hit tests to the correct ContentElement within the line. /// /// /// Offset within the line. /// ////// ContentElement which has been hit. /// internal IInputElement InputHitTest(int urOffset) { DependencyObject element = null; TextPointer position; TextPointerContext type = TextPointerContext.None; CharacterHit charIndex; int cp, delta; // Calculate shift in line offset to render trailing spaces or avoid clipping text delta = CalculateUOffsetShift(); if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { // We should not shift offset in this case Invariant.Assert(delta == 0); System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); // Get TextPointer from specified distance. charIndex = line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urOffset)); } else { // Get TextPointer from specified distance. charIndex = _line.GetCharacterHitFromDistance(TextDpi.FromTextDpi(urOffset - delta)); } cp = _paraClient.Paragraph.ParagraphStartCharacterPosition + charIndex.FirstCharacterIndex + charIndex.TrailingLength; position = TextContainerHelper.GetTextPointerFromCP(_paraClient.Paragraph.StructuralCache.TextContainer, cp, LogicalDirection.Forward) as TextPointer; if (position != null) { // If start of character, look forward. Otherwise, look backward. type = position.GetPointerContext((charIndex.TrailingLength == 0) ? LogicalDirection.Forward : LogicalDirection.Backward); // Get element only for Text & Start/End element, for all other positions // return null (it means that the line owner has been hit). if (type == TextPointerContext.Text || type == TextPointerContext.ElementEnd) { element = position.Parent; } else if (type == TextPointerContext.ElementStart) { element = position.GetAdjacentElementFromOuterPosition(LogicalDirection.Forward); } } return element as IInputElement; } ////// Get length of content hidden by ellipses. Return integer length of this content. /// internal int GetEllipsesLength() { // There are no ellipses, if: // * there is no overflow in the line // * text trimming is turned off if (!_line.HasOverflowed) { return 0; } if (TextParagraph.Properties.TextTrimming == TextTrimming.None) { return 0; } // Create collapsed text line to get length of collapsed content. System.Windows.Media.TextFormatting.TextLine collapsedLine = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(collapsedLine.HasCollapsed, "Line has not been collapsed"); IListcollapsedRanges = collapsedLine.GetTextCollapsedRanges(); if (collapsedRanges != null) { Invariant.Assert(collapsedRanges.Count == 1, "Multiple collapsed ranges are not supported."); TextCollapsedRange collapsedRange = collapsedRanges[0]; return collapsedRange.Length; } return 0; } /// /// Retrieves collection of GlyphRuns from a range of text. /// /// /// Glyph runs. /// /// /// Start dcp of range /// /// /// End dcp of range. /// internal void GetGlyphRuns(System.Collections.Generic.ListglyphRuns, int dcpStart, int dcpEnd) { // NOTE: Following logic is only temporary workaround for lack // of appropriate API that should be exposed by TextLine. int dcp = dcpStart - _dcp; int cch = dcpEnd - dcpStart; Debug.Assert(dcp >= 0 && (dcp + cch <= _line.Length)); IList > spans = _line.GetTextRunSpans(); DrawingGroup drawing = new DrawingGroup(); DrawingContext ctx = drawing.Open(); // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); _line.Draw(ctx, new Point(delta, 0), InvertAxes.None); ctx.Close(); // Copy glyph runs into separate array (for backward navigation). // And count number of chracters in the glyph runs collection. int cchGlyphRuns = 0; ArrayList glyphRunsCollection = new ArrayList(4); AddGlyphRunRecursive(drawing, glyphRunsCollection, ref cchGlyphRuns); Debug.Assert(cchGlyphRuns > 0 && glyphRunsCollection.Count > 0); // Count number of characters in text runs. int cchTextSpans = 0; foreach (TextSpan textSpan in spans) { if (textSpan.Value is TextCharacters) { cchTextSpans += textSpan.Length; } } // If number of characters in glyph runs is greater than number of characters // in text runs, it means that there is bullet at the beginning of the line // or hyphen at the end of the line. // For now hyphen case is ignored. // Remove those glyph runs from our colleciton. while (cchGlyphRuns > cchTextSpans) { GlyphRun glyphRun = (GlyphRun)glyphRunsCollection[0]; cchGlyphRuns -= (glyphRun.Characters == null ? 0 : glyphRun.Characters.Count); glyphRunsCollection.RemoveAt(0); } int curDcp = 0; int runIndex = 0; foreach (TextSpan span in spans) { if (span.Value is TextCharacters) { int cchRunsInSpan = 0; while (cchRunsInSpan < span.Length) { Invariant.Assert(runIndex < glyphRunsCollection.Count); GlyphRun run = (GlyphRun)glyphRunsCollection[runIndex]; int characterCount = (run.Characters == null ? 0 : run.Characters.Count); if ((dcp < curDcp + characterCount) && (dcp + cch > curDcp)) { glyphRuns.Add(run); } cchRunsInSpan += characterCount; ++runIndex; } Invariant.Assert(cchRunsInSpan == span.Length); // No need to continue, if dcpEnd has been reached. if (dcp + cch <= curDcp + span.Length) break; } curDcp += span.Length; } } /// /// Return text position for next caret position /// /// /// CharacterHit for current position /// internal CharacterHit GetNextCaretCharacterHit(CharacterHit index) { return _line.GetNextCaretCharacterHit(index); } ////// Return text position for previous caret position /// /// /// CharacterHit for current position /// internal CharacterHit GetPreviousCaretCharacterHit(CharacterHit index) { return _line.GetPreviousCaretCharacterHit(index); } ////// Return text position for backspace caret position /// /// /// CharacterHit for current position /// internal CharacterHit GetBackspaceCaretCharacterHit(CharacterHit index) { return _line.GetBackspaceCaretCharacterHit(index); } ////// Returns true of char hit is at caret unit boundary. /// /// /// CharacterHit to be tested. /// internal bool IsAtCaretCharacterHit(CharacterHit charHit) { return _line.IsAtCaretCharacterHit(charHit, _dcp); } #endregion Internal Methods //------------------------------------------------------------------- // // Internal Properties // //------------------------------------------------------------------- #region Internal Properties ////// Distance from the beginning of paragraph edge to the line edge. /// internal int Start { get { return TextDpi.ToTextDpi(_line.Start) + TextDpi.ToTextDpi(_indent) + CalculateUOffsetShift(); } } ////// Calculated width of the line. /// internal int Width { get { int width; if (IsWidthAdjusted) { width = TextDpi.ToTextDpi(_line.WidthIncludingTrailingWhitespace) - TextDpi.ToTextDpi(_indent); } else { width = TextDpi.ToTextDpi(_line.Width) - TextDpi.ToTextDpi(_indent); } Invariant.Assert(width >= 0, "Line width cannot be negative"); return width; } } ////// Height of the line; line advance distance. /// internal int Height { get { return TextDpi.ToTextDpi(_line.Height); } } ////// Baseline offset from the top of the line. /// internal int Baseline { get { return TextDpi.ToTextDpi(_line.Baseline); } } ////// True if last line of paragraph /// internal bool EndOfParagraph { get { // If there are no Newline characters, it is not the end of paragraph. if (_line.NewlineLength == 0) { return false; } // Since there are Newline characters in the line, do more expensive and // accurate check. return (((TextSpan)_runs[_runs.Count-1]).Value is ParagraphBreakRun); } } /// /// Length of the line including any synthetic characters. /// This length is PTS frendly. PTS does not like 0 length lines. /// internal int SafeLength { get { return _line.Length; } } ////// Length of the line excluding any synthetic characters. /// internal int ActualLength { get { return _line.Length - (EndOfParagraph ? _syntheticCharacterLength : 0); } } ////// Length of the line excluding any synthetic characters and line breaks. /// internal int ContentLength { get { return _line.Length - _line.NewlineLength; } } ////// Number of characters after the end of the line which may affect /// line wrapping. /// internal int DependantLength { get { return _line.DependentLength; } } ////// Was line truncated (forced broken)? /// internal bool IsTruncated { get { return _line.IsTruncated; } } ////// Formatting result of the line. /// internal PTS.FSFLRES FormattingResult { get { PTS.FSFLRES formatResult = PTS.FSFLRES.fsflrOutOfSpace; // If there are no Newline characters, we run out of space. if (_line.NewlineLength == 0) { return formatResult; } // Since there are Newline characters in the line, do more expensive and // accurate check. TextRun run = ((TextSpan)_runs[_runs.Count - 1]).Value as TextRun; if (run is ParagraphBreakRun) { formatResult = ((ParagraphBreakRun)run).BreakReason; } else if (run is LineBreakRun) { formatResult = ((LineBreakRun)run).BreakReason; } return formatResult; } } #endregion Internal Properties //------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------- #region Private Methods /// /// Returns true if there are any inline objects, false otherwise. /// private bool HasInlineObjects() { bool hasInlineObjects = false; foreach (TextSpantextSpan in _runs) { if (textSpan.Value is InlineObjectRun) { hasInlineObjects = true; break; } } return hasInlineObjects; } /// /// Returns bounds of an object/character at specified text index. /// /// /// Character index of an object/character /// /// /// Number of positions occupied by object/character /// /// /// Flow direction of object/character /// ///private Rect GetBoundsFromPosition(int cp, int cch, out FlowDirection flowDirection) { Rect rect; // Calculate shift in line offset to render trailing spaces or avoid clipping text double delta = TextDpi.FromTextDpi(CalculateUOffsetShift()); IList textBounds; if (_line.HasOverflowed && TextParagraph.Properties.TextTrimming != TextTrimming.None) { // We should not shift offset in this case Invariant.Assert(DoubleUtil.AreClose(delta, 0)); System.Windows.Media.TextFormatting.TextLine line = _line.Collapse(GetCollapsingProps(_wrappingWidth, TextParagraph.Properties)); Invariant.Assert(line.HasCollapsed, "Line has not been collapsed"); textBounds = line.GetTextBounds(cp, cch); } else { textBounds = _line.GetTextBounds(cp, cch); } Invariant.Assert(textBounds != null && textBounds.Count == 1, "Expecting exactly one TextBounds for a single text position."); IList runBounds = textBounds[0].TextRunBounds; if (runBounds != null) { Debug.Assert(runBounds.Count == 1, "Expecting exactly one TextRunBounds for a single text position."); rect = runBounds[0].Rectangle; } else { rect = textBounds[0].Rectangle; } flowDirection = textBounds[0].FlowDirection; rect.X = rect.X + delta; return rect; } /// /// Returns Line collapsing properties /// /// /// Wrapping width for collapsed line. /// /// /// Paragraph properties /// private TextCollapsingProperties GetCollapsingProps(double wrappingWidth, LineProperties paraProperties) { Invariant.Assert(paraProperties.TextTrimming != TextTrimming.None, "Text trimming must be enabled."); TextCollapsingProperties collapsingProps; if (paraProperties.TextTrimming == TextTrimming.CharacterEllipsis) { collapsingProps = new TextTrailingCharacterEllipsis(wrappingWidth, paraProperties.DefaultTextRunProperties); } else { collapsingProps = new TextTrailingWordEllipsis(wrappingWidth, paraProperties.DefaultTextRunProperties); } return collapsingProps; } ////// Perform depth-first search on a drawing tree to add all the glyph /// runs to the collection /// /// /// Drawing on which we perform DFS /// /// /// Glyph run collection. /// /// /// Character length of glyph run collection /// private void AddGlyphRunRecursive( Drawing drawing, IList glyphRunsCollection, ref int cchGlyphRuns) { DrawingGroup group = drawing as DrawingGroup; if (group != null) { foreach (Drawing child in group.Children) { AddGlyphRunRecursive(child, glyphRunsCollection, ref cchGlyphRuns); } } else { GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing; if (glyphRunDrawing != null) { // Add a glyph run GlyphRun glyphRun = glyphRunDrawing.GlyphRun; if (glyphRun != null) { cchGlyphRuns += (glyphRun.Characters == null ? 0 : glyphRun.Characters.Count); glyphRunsCollection.Add(glyphRun); } } } } ////// Returns amount of shift for X-offset to render trailing spaces /// internal int CalculateUOffsetShift() { int width; int trailingSpacesDelta = 0; // Calculate amount by which to to move line back if trailing spaces are rendered if (IsUOffsetAdjusted) { width = TextDpi.ToTextDpi(_line.WidthIncludingTrailingWhitespace); trailingSpacesDelta = TextDpi.ToTextDpi(_line.Width) - width; Invariant.Assert(trailingSpacesDelta <= 0); } else { width = TextDpi.ToTextDpi(_line.Width); trailingSpacesDelta = 0; } // Calculate amount to shift line forward in case we are clipping the front of the line. // If line is showing ellipsis do not perform this check since we should not be clipping the front // of the line anyway int widthDelta = 0; if ((_textAlignment == TextAlignment.Center || _textAlignment == TextAlignment.Right) && !ShowEllipses) { if (width > TextDpi.ToTextDpi(_wrappingWidth)) { widthDelta = width - TextDpi.ToTextDpi(_wrappingWidth); } else { widthDelta = 0; } } int totalShift; if (_textAlignment == TextAlignment.Center) { // Divide shift by two to center line totalShift = (int)((widthDelta + trailingSpacesDelta) / 2); } else { totalShift = widthDelta + trailingSpacesDelta; } return totalShift; } #endregion Private methods //------------------------------------------------------------------- // // Private Properties // //-------------------------------------------------------------------- #region Private Properties ////// True if line ends in hard break /// private bool HasLineBreak { get { return (_line.NewlineLength > 0); } } ////// True if line's X-offset needs adjustment to render trailing spaces /// private bool IsUOffsetAdjusted { get { return ((_textAlignment == TextAlignment.Right || _textAlignment == TextAlignment.Center) && IsWidthAdjusted); } } ////// True if line's width is adjusted to include trailing spaces. For right and center alignment we need to /// adjust line offset as well, but for left alignment we need to only make a width asjustment /// private bool IsWidthAdjusted { get { bool adjusted = false; // Trailing spaces rendered only around hard breaks if (HasLineBreak || EndOfParagraph) { // Lines with ellipsis are not shifted because ellipsis would not appear after trailing spaces if (!ShowEllipses) { adjusted = true; } } return adjusted; } } ////// True if eliipsis is displayed in the line /// private bool ShowEllipses { get { if (TextParagraph.Properties.TextTrimming == TextTrimming.None) { return false; } if (_line.HasOverflowed) { return true; } return false; } } ////// Text Paragraph this line is formatted for /// private TextParagraph TextParagraph { get { return _paraClient.Paragraph as TextParagraph; } } #endregion Private Properties //-------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields ////// TextFormatter host /// private readonly TextFormatterHost _host; ////// Character position at the beginning of text paragraph. All DCPs /// of the line are relative to this value. /// private readonly int _cpPara; ////// Line formatting context. Valid only during formatting. /// private FormattingContext _formattingContext; ////// Text line objects /// private System.Windows.Media.TextFormatting.TextLine _line; ////// Cached run list. This list needs to be in [....] with _line object. /// Every time the line is recreated, this list needs to be updated. /// private IList> _runs; /// /// Character position at the beginning of the line. /// private int _dcp; ////// Line wrapping width /// private double _wrappingWidth; ////// Track width (line width ignoring floats) /// private double _trackWidth = Double.NaN; ////// Is text mirrored? /// private bool _mirror; ////// Text indent. 0 for all lines except the first line, which maybe have non-zero indent. /// private double _indent; ////// TextAlignment of owner /// private TextAlignment _textAlignment; #endregion Private Fields // ------------------------------------------------------------------ // // FormattingContext Class // // ----------------------------------------------------------------- #region FormattingContext Class ////// Text line formatting context /// internal class FormattingContext { internal FormattingContext(bool measureMode, bool clearOnLeft, bool clearOnRight, TextRunCache textRunCache) { MeasureMode = measureMode; ClearOnLeft = clearOnLeft; ClearOnRight = clearOnRight; TextRunCache = textRunCache; LineFormatLengthTarget = -1; } internal TextRunCache TextRunCache; internal bool MeasureMode; internal bool ClearOnLeft; internal bool ClearOnRight; internal int LineFormatLengthTarget; } #endregion FormattingContext Class } } #pragma warning enable 1634, 1691 // 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
- EntitySet.cs
- ShellProvider.cs
- CqlIdentifiers.cs
- PowerStatus.cs
- SignatureResourcePool.cs
- TextReturnReader.cs
- PrimitiveXmlSerializers.cs
- PersonalizableAttribute.cs
- RegexRunner.cs
- SiteMembershipCondition.cs
- SamlSubject.cs
- StateDesigner.Helpers.cs
- Options.cs
- TreeNodeStyleCollection.cs
- ConsumerConnectionPointCollection.cs
- WebBrowserEvent.cs
- XmlAtomErrorReader.cs
- XmlValidatingReaderImpl.cs
- DataGridViewColumn.cs
- SequenceDesigner.cs
- XmlCodeExporter.cs
- SqlDataSourceWizardForm.cs
- HtmlAnchor.cs
- RouteItem.cs
- InstrumentationTracker.cs
- JavaScriptSerializer.cs
- DataServiceRequest.cs
- EditorAttribute.cs
- DataTableMapping.cs
- EntityDataSourceState.cs
- ProcessHostMapPath.cs
- PngBitmapDecoder.cs
- PostBackOptions.cs
- StylusPointProperty.cs
- PhonemeConverter.cs
- StringHandle.cs
- WebPartDescription.cs
- TabItem.cs
- ThreadPool.cs
- ConvertBinder.cs
- TripleDES.cs
- SystemParameters.cs
- UxThemeWrapper.cs
- Variant.cs
- smtpconnection.cs
- SimpleRecyclingCache.cs
- _NetRes.cs
- CellQuery.cs
- StylusOverProperty.cs
- PackWebResponse.cs
- MetadataArtifactLoaderResource.cs
- ListGeneralPage.cs
- Parsers.cs
- DataRowExtensions.cs
- MarkupExtensionSerializer.cs
- RemotingSurrogateSelector.cs
- Rules.cs
- PerformanceCounterPermissionEntry.cs
- DataGridViewColumnHeaderCell.cs
- _TransmitFileOverlappedAsyncResult.cs
- BooleanExpr.cs
- InputLanguageManager.cs
- Globals.cs
- DefaultSection.cs
- DetailsViewActionList.cs
- CaseInsensitiveComparer.cs
- DBSqlParserTable.cs
- SqlComparer.cs
- DataGridTextBoxColumn.cs
- CompositeActivityCodeGenerator.cs
- TextBoxRenderer.cs
- QilTargetType.cs
- CleanUpVirtualizedItemEventArgs.cs
- LinqDataSourceSelectEventArgs.cs
- GeometryCollection.cs
- Activator.cs
- ValueCollectionParameterReader.cs
- JoinElimination.cs
- WebBrowsableAttribute.cs
- FlowDocumentFormatter.cs
- NullExtension.cs
- DesignerOptions.cs
- NativeStructs.cs
- ObfuscationAttribute.cs
- InfoCardService.cs
- RadioButtonFlatAdapter.cs
- ObjectTokenCategory.cs
- Activator.cs
- ContainerUIElement3D.cs
- SqlInfoMessageEvent.cs
- GridLengthConverter.cs
- GeometryConverter.cs
- ClientConfigPaths.cs
- IISUnsafeMethods.cs
- ColorAnimationUsingKeyFrames.cs
- SafeRightsManagementQueryHandle.cs
- FormParameter.cs
- DesignerToolboxInfo.cs
- DataServiceQueryProvider.cs
- HorizontalAlignConverter.cs