Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Documents / FixedTextView.cs / 1 / FixedTextView.cs
//---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: TextView implementation for FixedDocument. // // History: // 10/29/2003 : zhenbinx - Created // 06/25/2004 : grzegorz - Performance work // 07/23/2004 : zhenbinx - Modify it to fit new per-DocumentPage TextView model // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using MS.Internal; using MS.Internal.Documents; using MS.Internal.Media; using MS.Utility; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows.Documents; using System.Windows.Controls; using System.Windows.Shapes; using System.Windows.Media; using System.Windows.Media.TextFormatting; // CharacterHit using System; ////// TextView for each individual FixedDocumentPage /// internal sealed class FixedTextView : TextViewBase { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors internal FixedTextView(FixedDocumentPage docPage) { _docPage = docPage; } #endregion Constructors //-------------------------------------------------------------------- // // Internal Methods // //------------------------------------------------------------------- #region Internal Methods ////// Retrieves a position matching a point. /// /// /// Point in pixel coordinates to test. /// /// /// If true, this method must always return a positioned text position /// (the closest position as calculated by the control's heuristics) /// unless the point is outside the boundaries of the page. /// If false, this method should return null position, if the test /// point does not fall within any character bounding box. /// ////// A text position and its orientation matching or closest to the point. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override ITextPointer GetTextPositionFromPoint(Point point, bool snapToText) { //Return ITextPointer to the end of this view in this special case if (point.Y == Double.MaxValue && point.X == Double.MaxValue) { FixedPosition fixedp; ITextPointer textPos = this.End; if (_GetFixedPosition(this.End, out fixedp)) { textPos = _CreateTextPointer(fixedp, LogicalDirection.Backward); if (textPos == null) { textPos = this.End; } } return textPos; } ITextPointer pos = null; UIElement e; bool isHit = _HitTest(point, out e); if (isHit) { Glyphs g = e as Glyphs; if (g != null) { pos = _CreateTextPointerFromGlyphs(g, point); } else if (e is Image) { Image im = (Image)e; FixedPosition fixedp = new FixedPosition(this.FixedPage.CreateFixedNode(this.PageIndex, im), 0); pos = _CreateTextPointer(fixedp, LogicalDirection.Forward); } else if (e is Path) { Path p = (Path)e; if (p.Fill is ImageBrush) { FixedPosition fixedp = new FixedPosition(this.FixedPage.CreateFixedNode(this.PageIndex, p), 0); pos = _CreateTextPointer(fixedp, LogicalDirection.Forward); } } } if (snapToText && pos == null) { pos = _SnapToText(point); Debug.Assert(pos != null); } DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("GetTextPositionFromPoint P{0}, STT={1}, CP={2}", point, snapToText, pos == null ? "null" : ((FixedTextPointer)pos).ToString())); return pos; } ////// Retrieves the height and offset, in pixels, of the edge of /// the object/character represented by position. /// /// /// Position of an object/character. /// /// /// Transform to be applied to returned rect /// ////// The height, in pixels, of the edge of the object/character /// represented by position. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// ////// Rect.Width is always 0. /// Output parameter Transform is always Identity. It is not expected that editing scenarios /// will require speparate transform with raw rectangle for this case. /// If the document is empty, then this method returns the expected /// height of a character, if placed at the specified position. /// internal override Rect GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform) { #if DEBUG DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("GetRectFromTextPosition {0}, {1}", (FixedTextPointer)position, position.LogicalDirection)); #endif FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; // need a default caret size, otherwise infinite corners cause text editor and MultiPageTextView problems. // Initialize transform to Identity. This function always returns Identity transform. Rect designRect = new Rect(0, 0, 0, 10); transform = Transform.Identity; Debug.Assert(ftp != null); if (ftp.FlowPosition.IsBoundary) { if (!_GetFirstFixedPosition(ftp, out fixedp)) { return designRect; } } else if (!_GetFixedPosition(ftp, out fixedp)) { // // This is the start/end element, we need to find out the next element and return the next element // start offset/height. // if (position.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.None) { ITextPointer psNext = position.CreatePointer(1); FixedTextPointer ftpNext = Container.VerifyPosition(psNext); if (!_GetFixedPosition(ftpNext, out fixedp)) { return designRect; } } else { return designRect; } } if (fixedp.Page != this.PageIndex) { return designRect; } DependencyObject element = this.FixedPage.GetElement(fixedp.Node); if (element is Glyphs) { Glyphs g = (Glyphs)element; designRect = _GetGlyphRunDesignRect(g, fixedp.Offset, fixedp.Offset); // need to do transform GeneralTransform tran = g.TransformToAncestor(this.FixedPage); designRect = _GetTransformedCaretRect(tran, designRect.TopLeft, designRect.Height); } else if (element is Image) { Image image = (Image)element; GeneralTransform tran = image.TransformToAncestor(this.FixedPage); Point offset = new Point(0, 0); if (fixedp.Offset > 0) { offset.X += image.ActualWidth; } designRect = _GetTransformedCaretRect(tran, offset, image.ActualHeight); } else if (element is Path) { Path path = (Path)element; GeneralTransform tran = path.TransformToAncestor(this.FixedPage); Rect bounds = path.Data.Bounds; Point offset = bounds.TopLeft; if (fixedp.Offset > 0) { offset.X += bounds.Width; } designRect = _GetTransformedCaretRect(tran, offset, bounds.Height); } return designRect; } ////// internal override Geometry GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition) { PathGeometry boundingGeometry = new PathGeometry(); Debug.Assert(startPosition != null && this.Contains(startPosition)); Debug.Assert(endPosition != null && this.Contains(endPosition)); Dictionary/// highlights = new Dictionary (); FixedTextPointer startftp = this.Container.VerifyPosition(startPosition); FixedTextPointer endftp = this.Container.VerifyPosition(endPosition); this.Container.GetMultiHighlights(startftp, endftp, highlights, FixedHighlightType.TextSelection, null, null); ArrayList highlightList; highlights.TryGetValue(this.FixedPage, out highlightList); if (highlightList != null) { foreach (FixedHighlight fh in highlightList) { if (fh.HighlightType == FixedHighlightType.None) { continue; } Rect backgroundRect = fh.ComputeDesignRect(); if (backgroundRect == Rect.Empty) { continue; } GeneralTransform transform = fh.Element.TransformToAncestor(this.FixedPage); Transform t = transform.AffineTransform; if (t == null) { t = Transform.Identity; } Glyphs g = fh.Glyphs; if (fh.Element.Clip != null) { Rect clipRect = fh.Element.Clip.Bounds; backgroundRect.Intersect(clipRect); } Geometry thisGeometry = new RectangleGeometry(backgroundRect); thisGeometry.Transform = t; backgroundRect = transform.TransformBounds(backgroundRect); boundingGeometry.AddGeometry(thisGeometry); } } return boundingGeometry; } /// /// Retrieves an oriented text position matching position advanced by /// a number of lines from its initial position. /// /// /// Initial text position of an object/character. /// /// /// The suggested X offset, in pixels, of text position on the destination /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise /// the method will try to find a position on the destination line closest /// to suggestedX. /// /// /// Number of lines to advance. Negative means move backwards. /// /// /// newSuggestedX is the offset at the position moved (useful when moving /// between columns or pages). /// /// /// linesMoved indicates the number of lines moved, which may be less /// than count if there is no more content. /// ////// A TextPointer and its orientation matching suggestedX on the /// destination line. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override ITextPointer GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved) { newSuggestedX = suggestedX; linesMoved = 0; #if DEBUG DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("FixedTextView.MoveToLine {0}, {1}, {2}, {3}", (FixedTextPointer)position, position.LogicalDirection, suggestedX, count)); #endif FixedPosition fixedp; LogicalDirection edge = position.LogicalDirection; LogicalDirection scanDir = LogicalDirection.Forward; ITextPointer pos = position; FixedTextPointer ftp = Container.VerifyPosition(position); FixedTextPointer nav = new FixedTextPointer(true, edge, (FlowPosition)ftp.FlowPosition.Clone()); //Skip any formatting tags _SkipFormattingTags(nav); bool gotFixedPosition = false; if ( count == 0 || ((gotFixedPosition = _GetFixedPosition(nav, out fixedp)) && fixedp.Page != this.PageIndex ) ) { //Invalid text position, so do nothing. PS #963924 return position; } if (count < 0) { count = -count; scanDir = LogicalDirection.Backward; } if (!gotFixedPosition) { // move to next insertion position in direction, provided we're in this view if (this.Contains(position)) { nav = new FixedTextPointer(true, scanDir, (FlowPosition)ftp.FlowPosition.Clone()); ((ITextPointer)nav).MoveToInsertionPosition(scanDir); ((ITextPointer)nav).MoveToNextInsertionPosition(scanDir); if (this.Contains(nav)) { // make sure we haven't changed pages linesMoved = (scanDir == LogicalDirection.Forward) ? 1 : -1; return nav; } } return position; } if (DoubleUtil.IsNaN(suggestedX)) { suggestedX = 0; } while (count > linesMoved) { if (!_GetNextLineGlyphs(ref fixedp, ref edge, suggestedX, scanDir)) break; linesMoved++; } if (linesMoved == 0) { pos = position.CreatePointer(); return pos; } if (scanDir == LogicalDirection.Backward) { linesMoved = -linesMoved; } pos = _CreateTextPointer(fixedp, edge); Debug.Assert(pos != null); if (pos.CompareTo(position) == 0) { linesMoved = 0; } return pos; } ////// Determines if a position is located between two caret units. /// /// /// Position to test. /// ////// Returns true if the specified position precedes or follows /// the first or last code point of a caret unit, respectively. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// ////// In the context of this method, "caret unit" refers to a group /// of one or more Unicode code points that map to a single rendered /// glyph. /// internal override bool IsAtCaretUnitBoundary(ITextPointer position) { FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; if (_GetFixedPosition(ftp, out fixedp)) { DependencyObject element = this.FixedPage.GetElement(fixedp.Node); if (element is Glyphs) { Glyphs g = (Glyphs)element; int characterCount = (g.UnicodeString == null ? 0 : g.UnicodeString.Length); if (fixedp.Offset == characterCount) { //end of line -- allow caret return true; } else { GlyphRun run = g.MeasurementGlyphRun; return run.CaretStops == null || run.CaretStops[fixedp.Offset]; } } else if (element is Image || element is Path) { //support caret before and after image return true; } else { // No text position should be on any other type of element Debug.Assert(false); } } return false; } ////// Finds the next position at the edge of a caret unit in /// specified direction. /// /// /// Initial text position of an object/character. /// /// /// If Forward, this method returns the "caret unit" position following /// the initial position. /// If Backward, this method returns the caret unit" position preceding /// the initial position. /// ////// The next caret unit break position in specified direction. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// ////// In the context of this method, "caret unit" refers to a group of one /// or more Unicode code points that map to a single rendered glyph. /// /// If position is located between two caret units, this method returns /// a new position located at the opposite edge of the caret unit in /// the indicated direction. /// If position is located within a group of Unicode code points that map /// to a single caret unit, this method returns a new position at /// the indicated edge of the containing caret unit. /// If position is located at the beginning of end of content -- there is /// no content in the indicated direction -- then this method returns /// a position located at the same location as initial position. /// internal override ITextPointer GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction) { FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; if (_GetFixedPosition(ftp, out fixedp)) { DependencyObject element = this.FixedPage.GetElement(fixedp.Node); if (element is Glyphs) { Glyphs g = (Glyphs)element; GlyphRun run = g.ToGlyphRun(); int characterCount = (run.Characters == null) ? 0 : run.Characters.Count; CharacterHit start = (fixedp.Offset == characterCount) ? new CharacterHit(fixedp.Offset - 1, 1) : new CharacterHit(fixedp.Offset, 0); CharacterHit next = (direction == LogicalDirection.Forward) ? run.GetNextCaretCharacterHit(start) : run.GetPreviousCaretCharacterHit(start); if (!start.Equals(next)) { LogicalDirection edge = LogicalDirection.Forward; if (next.TrailingLength > 0) { edge = LogicalDirection.Backward; } int index = next.FirstCharacterIndex + next.TrailingLength; return _CreateTextPointer(new FixedPosition(fixedp.Node, index), edge); } } } //default behavior is to simply move textpointer ITextPointer pointer = position.CreatePointer(); pointer.MoveToNextInsertionPosition(direction); return pointer; } ////// Finds the previous position at the edge of a caret after backspacing. /// /// /// Initial text position of an object/character. /// ////// The previous caret unit break position after backspacing. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override ITextPointer GetBackspaceCaretUnitPosition(ITextPointer position) { throw new NotImplementedException(); } ////// Returns a TextSegment that spans the line on which position is located. /// /// /// Any oriented text position on the line. /// ////// TextSegment that spans the line on which position is located. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override TextSegment GetLineRange(ITextPointer position) { #if DEBUG DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("GetLineRange {0}, {1}", (FixedTextPointer)position, position.LogicalDirection)); #endif FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; if (!_GetFixedPosition(ftp, out fixedp)) { return new TextSegment(position, position, true); } int count = 0; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetNextLine(fixedp.Node, true, ref count); if (fixedNodes == null) { // This will happen in the case of images fixedNodes = new FixedNode[] { fixedp.Node }; } FixedNode lastNode = fixedNodes[fixedNodes.Length - 1]; DependencyObject element = FixedPage.GetElement(lastNode); int lastIndex = 1; if (element is Glyphs) { lastIndex = ((Glyphs)element).UnicodeString.Length; } ITextPointer begin = _CreateTextPointer(new FixedPosition(fixedNodes[0], 0), LogicalDirection.Forward); ITextPointer end = _CreateTextPointer(new FixedPosition(lastNode, lastIndex), LogicalDirection.Backward); if (begin.CompareTo(end) > 0) { ITextPointer temp = begin; begin = end; end = temp; } return new TextSegment(begin, end, true); } ////// Determines whenever TextView contains specified position. /// /// /// A position to test. /// ////// True if TextView contains specified text position. /// Otherwise returns false. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override bool Contains(ITextPointer position) { FixedTextPointer tp = Container.VerifyPosition(position); return ((tp.CompareTo(this.Start) > 0 && tp.CompareTo(this.End) < 0) || (tp.CompareTo(this.Start) == 0 && (tp.LogicalDirection == LogicalDirection.Forward || this.IsContainerStart)) || (tp.CompareTo(this.End) == 0 && (tp.LogicalDirection == LogicalDirection.Backward || this.IsContainerEnd)) ); } ////// Makes sure that TextView is in a clean layout state and it is /// possible to retrieve layout related data. /// ////// If IsValid returns false, it is required to call this method /// before calling any other method on TextView. /// Validate method might be very expensive, because it may lead /// to full layout update. /// internal override bool Validate() { // Always valid. Do nothing. return true; } #endregion Internal Methods //-------------------------------------------------------------------- // // Internal Properties // //-------------------------------------------------------------------- #region Internal Properties ////// internal override UIElement RenderScope { get { Visual visual = _docPage.Visual; while (visual != null && !(visual is UIElement)) { visual = VisualTreeHelper.GetParent(visual) as Visual; } return visual as UIElement; } } ////// TextContainer that stores content. /// internal override ITextContainer TextContainer { get { return this.Container; } } ////// Determines whenever layout is in clean state and it is possible /// to retrieve layout related data. /// internal override bool IsValid { get { return true; } } ////// internal override bool RendersOwnSelection { get { return true; } } ////// /// Collection of TextSegments representing content of the TextView. /// internal override ReadOnlyCollectionTextSegments { get { if (_textSegments == null) { List list = new List (1); list.Add(new TextSegment(this.Start, this.End, true)); _textSegments = new ReadOnlyCollection (list); } return _textSegments; } } internal FixedTextPointer Start { get { if (_start == null) { FlowPosition flowStart = Container.FixedTextBuilder.GetPageStartFlowPosition(this.PageIndex); _start = new FixedTextPointer(false, LogicalDirection.Forward, flowStart); } return _start; } } internal FixedTextPointer End { get { if (_end == null) { FlowPosition flowEnd = Container.FixedTextBuilder.GetPageEndFlowPosition(this.PageIndex); _end = new FixedTextPointer(false, LogicalDirection.Backward, flowEnd); } return _end; } } #endregion Internal Properties //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ #region Private Methods // hit testing to find the inner most UIElement that was hit // as well as the containing fixed page. private bool _HitTest(Point pt, out UIElement e) { e = null; HitTestResult result = VisualTreeHelper.HitTest(this.FixedPage, pt); DependencyObject v = (result != null) ? result.VisualHit : null; while (v != null) { DependencyObjectType t = v.DependencyObjectType; if (t == UIElementType || t.IsSubclassOf(UIElementType)) { e = (UIElement) v; return true; } v = VisualTreeHelper.GetParent(v); } return false; } private void _GlyphRunHitTest(Glyphs g, double xoffset, out int charIndex, out LogicalDirection edge) { charIndex = 0; edge = LogicalDirection.Forward; GlyphRun run = g.ToGlyphRun(); bool isInside; double distance; if ((run.BidiLevel & 1) != 0) { distance = run.BaselineOrigin.X - xoffset; } else { distance = xoffset - run.BaselineOrigin.X; } CharacterHit hit = run.GetCaretCharacterHitFromDistance(distance, out isInside); charIndex = hit.FirstCharacterIndex + hit.TrailingLength; edge = (hit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward; } private ITextPointer _SnapToText(Point point) { ITextPointer itp = null; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetLine(this.PageIndex, point); if (fixedNodes != null && fixedNodes.Length > 0) { double closestDistance = Double.MaxValue; double xoffset = 0; Glyphs closestGlyphs = null; FixedNode closestNode = fixedNodes[0]; foreach (FixedNode node in fixedNodes) { Glyphs startGlyphs = this.FixedPage.GetGlyphsElement(node); GeneralTransform tranToGlyphs = this.FixedPage.TransformToDescendant(startGlyphs); Point transformedPt = point; if (tranToGlyphs != null) { tranToGlyphs.TryTransform(transformedPt, out transformedPt); } GlyphRun run = startGlyphs.ToGlyphRun(); Rect alignmentRect = run.ComputeAlignmentBox(); alignmentRect.Offset(startGlyphs.OriginX, startGlyphs.OriginY); double horizontalDistance = Math.Max(0, (transformedPt.X > alignmentRect.X) ? (transformedPt.X - alignmentRect.Right) : (alignmentRect.X - transformedPt.X)); double verticalDistance = Math.Max(0, (transformedPt.Y > alignmentRect.Y) ? (transformedPt.Y - alignmentRect.Bottom) : (alignmentRect.Y - transformedPt.Y)); double manhattanDistance = horizontalDistance + verticalDistance; if (closestGlyphs == null || manhattanDistance < closestDistance) { closestDistance = manhattanDistance; closestGlyphs = startGlyphs; closestNode = node; xoffset = transformedPt.X; } } int index; LogicalDirection dir; _GlyphRunHitTest(closestGlyphs, xoffset, out index, out dir); FixedPosition fixedp = new FixedPosition(closestNode, index); itp = _CreateTextPointer(fixedp, dir); Debug.Assert(itp != null); } else { // // That condition is only possible when there is no line in the page // if (point.Y < this.FixedPage.Height / 2) { itp = ((ITextPointer)this.Start).CreatePointer(LogicalDirection.Forward); itp.MoveToInsertionPosition(LogicalDirection.Forward); } else { itp = ((ITextPointer)this.End).CreatePointer(LogicalDirection.Backward); itp.MoveToInsertionPosition(LogicalDirection.Backward); } } return itp; } // If return false, nothing has been modified, which implies out of page boundary // The input of suggestedX is in the VisualRoot's cooridnate system private bool _GetNextLineGlyphs(ref FixedPosition fixedp, ref LogicalDirection edge, double suggestedX, LogicalDirection scanDir) { int count = 1; int pageIndex = fixedp.Page; bool moved = false; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetNextLine(fixedp.Node, (scanDir == LogicalDirection.Forward), ref count); if (fixedNodes != null && fixedNodes.Length > 0) { FixedPage page = Container.FixedDocument.SyncGetPage(pageIndex, false); // This line contains multiple Glyhs. Scan backward // util we hit the first one whose OriginX is smaller // then suggestedX; if (Double.IsInfinity(suggestedX)) { suggestedX = 0; } Point topOfPage = new Point(suggestedX, 0); Point secondPoint = new Point(suggestedX, 1000); FixedNode hitNode = fixedNodes[0]; Glyphs hitGlyphs = null; double closestDistance = Double.MaxValue; double xoffset = 0; for (int i = fixedNodes.Length - 1; i >= 0; i--) { FixedNode node = fixedNodes[i]; Glyphs g = page.GetGlyphsElement(node); if (g != null) { GeneralTransform transform = page.TransformToDescendant(g); Point pt1 = topOfPage; Point pt2 = secondPoint; if (transform != null) { transform.TryTransform(pt1, out pt1); transform.TryTransform(pt2, out pt2); } double invSlope = (pt2.X - pt1.X) / (pt2.Y - pt1.Y); double xoff, distance; GlyphRun run = g.ToGlyphRun(); Rect box = run.ComputeAlignmentBox(); box.Offset(g.OriginX, g.OriginY); if (invSlope > 1000 || invSlope < -1000) { // special case for vertical text xoff = 0; distance = (pt1.Y > box.Y) ? (pt1.Y - box.Bottom) : (box.Y - pt1.Y); } else { double centerY = (box.Top + box.Bottom) / 2; xoff = pt1.X + invSlope * (centerY - pt1.Y); distance = (xoff > box.X) ? (xoff - box.Right) : (box.X - xoff); } if (distance < closestDistance) { closestDistance = distance; xoffset = xoff; hitNode = node; hitGlyphs = g; if (distance <= 0) { break; } } } } Debug.Assert(hitGlyphs != null); int charIdx; _GlyphRunHitTest(hitGlyphs, xoffset, out charIdx, out edge); fixedp = new FixedPosition(hitNode, charIdx); moved = true; } return moved; } private static double _GetDistanceToCharacter(GlyphRun run, int charOffset) { int firstChar = charOffset, trailingLength = 0; int characterCount = (run.Characters == null) ? 0 : run.Characters.Count; if (firstChar == characterCount) { // place carat at end of previous character to make sure it works at end of line firstChar--; trailingLength = 1; } return run.GetDistanceFromCaretCharacterHit(new CharacterHit(firstChar, trailingLength)); } // char index == -1 implies end of run. internal static Rect _GetGlyphRunDesignRect(Glyphs g, int charStart, int charEnd) { GlyphRun run = g.ToGlyphRun(); if (run == null) { return Rect.Empty; } Rect designRect = run.ComputeAlignmentBox(); designRect.Offset(run.BaselineOrigin.X, run.BaselineOrigin.Y); int charCount = 0; if (run.Characters != null) { charCount = run.Characters.Count; } else if (g.UnicodeString != null) { charCount = g.UnicodeString.Length; } if (charStart > charCount) { //Extra space was added at the end of the run for contiguity Debug.Assert(charStart - charCount == 1); charStart = charCount; } else if (charStart < 0) { //This is a reversed run charStart = 0; } if (charEnd > charCount) { //Extra space was added at the end of the run for contiguity Debug.Assert(charEnd - charCount == 1); charEnd = charCount; } else if (charEnd < 0) { //This is a reversed run charEnd = 0; } double x1 = _GetDistanceToCharacter(run, charStart); double x2 = _GetDistanceToCharacter(run, charEnd); double width = x2 - x1; if ((run.BidiLevel & 1) != 0) { // right to left designRect.X = run.BaselineOrigin.X - x2; } else { designRect.X = run.BaselineOrigin.X + x1; } designRect.Width = width; return designRect; } // Creates an axis-aligned caret for possibly rotated glyphs private Rect _GetTransformedCaretRect(GeneralTransform transform, Point origin, double height) { Point bottom = origin; bottom.Y += height; transform.TryTransform(origin, out origin); transform.TryTransform(bottom, out bottom); Rect caretRect = new Rect(origin, bottom); if (caretRect.Width > 0) { // only vertical carets are supported by TextEditor // What to do if height == 0? caretRect.X += caretRect.Width / 2; caretRect.Width = 0; } if (caretRect.Height < 1) { caretRect.Height = 1; } return caretRect; } //------------------------------------------------------------------- // FlowPosition --> FixedPosition //--------------------------------------------------------------------- // Helper function to overcome the limitation in FixedTextBuilder.GetFixedPosition // Making sure we are asking a position that is either a Run or an EmbeddedElement. private bool _GetFixedPosition(FixedTextPointer ftp, out FixedPosition fixedp) { LogicalDirection textdir = ftp.LogicalDirection; TextPointerContext symbolType = ((ITextPointer)ftp).GetPointerContext(textdir); if (ftp.FlowPosition.IsBoundary || symbolType == TextPointerContext.None) { return _GetFirstFixedPosition(ftp, out fixedp); } if (symbolType == TextPointerContext.ElementStart || symbolType == TextPointerContext.ElementEnd) { //Try to find the first valid insertion position if exists switch (symbolType) { case TextPointerContext.ElementStart: textdir = LogicalDirection.Forward; break; case TextPointerContext.ElementEnd: textdir = LogicalDirection.Backward; break; } FixedTextPointer nav = new FixedTextPointer(true, textdir, (FlowPosition)ftp.FlowPosition.Clone()); _SkipFormattingTags(nav); symbolType = ((ITextPointer)nav).GetPointerContext(textdir); if (symbolType != TextPointerContext.Text && symbolType != TextPointerContext.EmbeddedElement) { //Try moving to the next insertion position if (((ITextPointer)nav).MoveToNextInsertionPosition(textdir) && this.Container.GetPageNumber(nav) == this.PageIndex) { return Container.FixedTextBuilder.GetFixedPosition(nav.FlowPosition, textdir, out fixedp); } else { fixedp = new FixedPosition(this.Container.FixedTextBuilder.FixedFlowMap.FixedStartEdge, 0); return false; } } else { ftp = nav; } } Debug.Assert(symbolType == TextPointerContext.Text || symbolType == TextPointerContext.EmbeddedElement); return Container.FixedTextBuilder.GetFixedPosition(ftp.FlowPosition, textdir, out fixedp); } //Find the first valid insertion position after or before the boundary node private bool _GetFirstFixedPosition(FixedTextPointer ftp, out FixedPosition fixedP) { LogicalDirection dir = LogicalDirection.Forward; if (ftp.FlowPosition.FlowNode.Fp != 0) { //End boundary dir = LogicalDirection.Backward; } FlowPosition flowP = (FlowPosition) ftp.FlowPosition.Clone(); //Get the first node that comes before or after the boundary node(probably a start or end node) flowP.Move(dir); FixedTextPointer nav = new FixedTextPointer(true, dir, flowP); if (flowP.IsStart || flowP.IsEnd) { ((ITextPointer)nav).MoveToNextInsertionPosition(dir); } if (this.Container.GetPageNumber(nav) == this.PageIndex) { return Container.FixedTextBuilder.GetFixedPosition(nav.FlowPosition, dir, out fixedP); } else { //This position is on another page. fixedP = new FixedPosition(this.Container.FixedTextBuilder.FixedFlowMap.FixedStartEdge, 0); return false; } } //------------------------------------------------------------------- // FixedPosition --> ITextPointer //---------------------------------------------------------------------- // Create a text position from description of a fixed position. // This is primarily called from HitTesting code private ITextPointer _CreateTextPointer(FixedPosition fixedPosition, LogicalDirection edge) { // Create a FlowPosition to represent this fixed position FlowPosition flowHit = Container.FixedTextBuilder.CreateFlowPosition(fixedPosition); if (flowHit != null) { DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("_CreatetTextPointer {0}:{1}", fixedPosition.ToString(), flowHit.ToString())); // Create a TextPointer from the flow position return new FixedTextPointer(true, edge, flowHit); } return null; } // Create a text position from description of a fixed position. // This is primarily called from HitTesting code private ITextPointer _CreateTextPointerFromGlyphs(Glyphs g, Point point) { GeneralTransform transform = this.VisualRoot.TransformToDescendant(g); if (transform != null) { transform.TryTransform(point, out point); } int charIndex; LogicalDirection edge; _GlyphRunHitTest(g, point.X, out charIndex, out edge); FixedPosition fixedp = new FixedPosition(this.FixedPage.CreateFixedNode(this.PageIndex, g), charIndex); return _CreateTextPointer(fixedp, edge); } private void _SkipFormattingTags(ITextPointer textPointer) { Debug.Assert(!textPointer.IsFrozen, "Can't reposition a frozen pointer!"); LogicalDirection dir = textPointer.LogicalDirection; int increment = (dir == LogicalDirection.Forward ? +1 : -1); while (TextSchema.IsFormattingType( textPointer.GetElementType(dir)) ) { textPointer.MoveByOffset(increment); } } #endregion Private methods //----------------------------------------------------- // // Private Properties // //------------------------------------------------------ #region Private Properties private FixedTextContainer Container { get { return (FixedTextContainer)_docPage.TextContainer; } } // The visual node that is root of this TextView's visual tree private Visual VisualRoot { get { return this._docPage.Visual; } } // The FixedPage that provides content for this view private FixedPage FixedPage { get { return this._docPage.FixedPage; } } // private int PageIndex { get { return this._docPage.PageIndex; } } private bool IsContainerStart { get { return (this.Start.CompareTo(this.TextContainer.Start) == 0); } } private bool IsContainerEnd { get { return (this.End.CompareTo(this.TextContainer.End) == 0); } } #endregion Private Properties //-------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields private readonly FixedDocumentPage _docPage; // Cache for start/end private FixedTextPointer _start; private FixedTextPointer _end; private ReadOnlyCollection _textSegments; /// /// Caches the UIElement's DependencyObjectType. /// private static DependencyObjectType UIElementType = DependencyObjectType.FromSystemTypeInternal(typeof(UIElement)); #endregion Private Fields } } // 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. // // // Description: TextView implementation for FixedDocument. // // History: // 10/29/2003 : zhenbinx - Created // 06/25/2004 : grzegorz - Performance work // 07/23/2004 : zhenbinx - Modify it to fit new per-DocumentPage TextView model // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using MS.Internal; using MS.Internal.Documents; using MS.Internal.Media; using MS.Utility; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows.Documents; using System.Windows.Controls; using System.Windows.Shapes; using System.Windows.Media; using System.Windows.Media.TextFormatting; // CharacterHit using System; ////// TextView for each individual FixedDocumentPage /// internal sealed class FixedTextView : TextViewBase { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors internal FixedTextView(FixedDocumentPage docPage) { _docPage = docPage; } #endregion Constructors //-------------------------------------------------------------------- // // Internal Methods // //------------------------------------------------------------------- #region Internal Methods ////// Retrieves a position matching a point. /// /// /// Point in pixel coordinates to test. /// /// /// If true, this method must always return a positioned text position /// (the closest position as calculated by the control's heuristics) /// unless the point is outside the boundaries of the page. /// If false, this method should return null position, if the test /// point does not fall within any character bounding box. /// ////// A text position and its orientation matching or closest to the point. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override ITextPointer GetTextPositionFromPoint(Point point, bool snapToText) { //Return ITextPointer to the end of this view in this special case if (point.Y == Double.MaxValue && point.X == Double.MaxValue) { FixedPosition fixedp; ITextPointer textPos = this.End; if (_GetFixedPosition(this.End, out fixedp)) { textPos = _CreateTextPointer(fixedp, LogicalDirection.Backward); if (textPos == null) { textPos = this.End; } } return textPos; } ITextPointer pos = null; UIElement e; bool isHit = _HitTest(point, out e); if (isHit) { Glyphs g = e as Glyphs; if (g != null) { pos = _CreateTextPointerFromGlyphs(g, point); } else if (e is Image) { Image im = (Image)e; FixedPosition fixedp = new FixedPosition(this.FixedPage.CreateFixedNode(this.PageIndex, im), 0); pos = _CreateTextPointer(fixedp, LogicalDirection.Forward); } else if (e is Path) { Path p = (Path)e; if (p.Fill is ImageBrush) { FixedPosition fixedp = new FixedPosition(this.FixedPage.CreateFixedNode(this.PageIndex, p), 0); pos = _CreateTextPointer(fixedp, LogicalDirection.Forward); } } } if (snapToText && pos == null) { pos = _SnapToText(point); Debug.Assert(pos != null); } DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("GetTextPositionFromPoint P{0}, STT={1}, CP={2}", point, snapToText, pos == null ? "null" : ((FixedTextPointer)pos).ToString())); return pos; } ////// Retrieves the height and offset, in pixels, of the edge of /// the object/character represented by position. /// /// /// Position of an object/character. /// /// /// Transform to be applied to returned rect /// ////// The height, in pixels, of the edge of the object/character /// represented by position. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// ////// Rect.Width is always 0. /// Output parameter Transform is always Identity. It is not expected that editing scenarios /// will require speparate transform with raw rectangle for this case. /// If the document is empty, then this method returns the expected /// height of a character, if placed at the specified position. /// internal override Rect GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform) { #if DEBUG DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("GetRectFromTextPosition {0}, {1}", (FixedTextPointer)position, position.LogicalDirection)); #endif FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; // need a default caret size, otherwise infinite corners cause text editor and MultiPageTextView problems. // Initialize transform to Identity. This function always returns Identity transform. Rect designRect = new Rect(0, 0, 0, 10); transform = Transform.Identity; Debug.Assert(ftp != null); if (ftp.FlowPosition.IsBoundary) { if (!_GetFirstFixedPosition(ftp, out fixedp)) { return designRect; } } else if (!_GetFixedPosition(ftp, out fixedp)) { // // This is the start/end element, we need to find out the next element and return the next element // start offset/height. // if (position.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.None) { ITextPointer psNext = position.CreatePointer(1); FixedTextPointer ftpNext = Container.VerifyPosition(psNext); if (!_GetFixedPosition(ftpNext, out fixedp)) { return designRect; } } else { return designRect; } } if (fixedp.Page != this.PageIndex) { return designRect; } DependencyObject element = this.FixedPage.GetElement(fixedp.Node); if (element is Glyphs) { Glyphs g = (Glyphs)element; designRect = _GetGlyphRunDesignRect(g, fixedp.Offset, fixedp.Offset); // need to do transform GeneralTransform tran = g.TransformToAncestor(this.FixedPage); designRect = _GetTransformedCaretRect(tran, designRect.TopLeft, designRect.Height); } else if (element is Image) { Image image = (Image)element; GeneralTransform tran = image.TransformToAncestor(this.FixedPage); Point offset = new Point(0, 0); if (fixedp.Offset > 0) { offset.X += image.ActualWidth; } designRect = _GetTransformedCaretRect(tran, offset, image.ActualHeight); } else if (element is Path) { Path path = (Path)element; GeneralTransform tran = path.TransformToAncestor(this.FixedPage); Rect bounds = path.Data.Bounds; Point offset = bounds.TopLeft; if (fixedp.Offset > 0) { offset.X += bounds.Width; } designRect = _GetTransformedCaretRect(tran, offset, bounds.Height); } return designRect; } ////// internal override Geometry GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition) { PathGeometry boundingGeometry = new PathGeometry(); Debug.Assert(startPosition != null && this.Contains(startPosition)); Debug.Assert(endPosition != null && this.Contains(endPosition)); Dictionary/// highlights = new Dictionary (); FixedTextPointer startftp = this.Container.VerifyPosition(startPosition); FixedTextPointer endftp = this.Container.VerifyPosition(endPosition); this.Container.GetMultiHighlights(startftp, endftp, highlights, FixedHighlightType.TextSelection, null, null); ArrayList highlightList; highlights.TryGetValue(this.FixedPage, out highlightList); if (highlightList != null) { foreach (FixedHighlight fh in highlightList) { if (fh.HighlightType == FixedHighlightType.None) { continue; } Rect backgroundRect = fh.ComputeDesignRect(); if (backgroundRect == Rect.Empty) { continue; } GeneralTransform transform = fh.Element.TransformToAncestor(this.FixedPage); Transform t = transform.AffineTransform; if (t == null) { t = Transform.Identity; } Glyphs g = fh.Glyphs; if (fh.Element.Clip != null) { Rect clipRect = fh.Element.Clip.Bounds; backgroundRect.Intersect(clipRect); } Geometry thisGeometry = new RectangleGeometry(backgroundRect); thisGeometry.Transform = t; backgroundRect = transform.TransformBounds(backgroundRect); boundingGeometry.AddGeometry(thisGeometry); } } return boundingGeometry; } /// /// Retrieves an oriented text position matching position advanced by /// a number of lines from its initial position. /// /// /// Initial text position of an object/character. /// /// /// The suggested X offset, in pixels, of text position on the destination /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise /// the method will try to find a position on the destination line closest /// to suggestedX. /// /// /// Number of lines to advance. Negative means move backwards. /// /// /// newSuggestedX is the offset at the position moved (useful when moving /// between columns or pages). /// /// /// linesMoved indicates the number of lines moved, which may be less /// than count if there is no more content. /// ////// A TextPointer and its orientation matching suggestedX on the /// destination line. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override ITextPointer GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved) { newSuggestedX = suggestedX; linesMoved = 0; #if DEBUG DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("FixedTextView.MoveToLine {0}, {1}, {2}, {3}", (FixedTextPointer)position, position.LogicalDirection, suggestedX, count)); #endif FixedPosition fixedp; LogicalDirection edge = position.LogicalDirection; LogicalDirection scanDir = LogicalDirection.Forward; ITextPointer pos = position; FixedTextPointer ftp = Container.VerifyPosition(position); FixedTextPointer nav = new FixedTextPointer(true, edge, (FlowPosition)ftp.FlowPosition.Clone()); //Skip any formatting tags _SkipFormattingTags(nav); bool gotFixedPosition = false; if ( count == 0 || ((gotFixedPosition = _GetFixedPosition(nav, out fixedp)) && fixedp.Page != this.PageIndex ) ) { //Invalid text position, so do nothing. PS #963924 return position; } if (count < 0) { count = -count; scanDir = LogicalDirection.Backward; } if (!gotFixedPosition) { // move to next insertion position in direction, provided we're in this view if (this.Contains(position)) { nav = new FixedTextPointer(true, scanDir, (FlowPosition)ftp.FlowPosition.Clone()); ((ITextPointer)nav).MoveToInsertionPosition(scanDir); ((ITextPointer)nav).MoveToNextInsertionPosition(scanDir); if (this.Contains(nav)) { // make sure we haven't changed pages linesMoved = (scanDir == LogicalDirection.Forward) ? 1 : -1; return nav; } } return position; } if (DoubleUtil.IsNaN(suggestedX)) { suggestedX = 0; } while (count > linesMoved) { if (!_GetNextLineGlyphs(ref fixedp, ref edge, suggestedX, scanDir)) break; linesMoved++; } if (linesMoved == 0) { pos = position.CreatePointer(); return pos; } if (scanDir == LogicalDirection.Backward) { linesMoved = -linesMoved; } pos = _CreateTextPointer(fixedp, edge); Debug.Assert(pos != null); if (pos.CompareTo(position) == 0) { linesMoved = 0; } return pos; } ////// Determines if a position is located between two caret units. /// /// /// Position to test. /// ////// Returns true if the specified position precedes or follows /// the first or last code point of a caret unit, respectively. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// ////// In the context of this method, "caret unit" refers to a group /// of one or more Unicode code points that map to a single rendered /// glyph. /// internal override bool IsAtCaretUnitBoundary(ITextPointer position) { FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; if (_GetFixedPosition(ftp, out fixedp)) { DependencyObject element = this.FixedPage.GetElement(fixedp.Node); if (element is Glyphs) { Glyphs g = (Glyphs)element; int characterCount = (g.UnicodeString == null ? 0 : g.UnicodeString.Length); if (fixedp.Offset == characterCount) { //end of line -- allow caret return true; } else { GlyphRun run = g.MeasurementGlyphRun; return run.CaretStops == null || run.CaretStops[fixedp.Offset]; } } else if (element is Image || element is Path) { //support caret before and after image return true; } else { // No text position should be on any other type of element Debug.Assert(false); } } return false; } ////// Finds the next position at the edge of a caret unit in /// specified direction. /// /// /// Initial text position of an object/character. /// /// /// If Forward, this method returns the "caret unit" position following /// the initial position. /// If Backward, this method returns the caret unit" position preceding /// the initial position. /// ////// The next caret unit break position in specified direction. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// ////// In the context of this method, "caret unit" refers to a group of one /// or more Unicode code points that map to a single rendered glyph. /// /// If position is located between two caret units, this method returns /// a new position located at the opposite edge of the caret unit in /// the indicated direction. /// If position is located within a group of Unicode code points that map /// to a single caret unit, this method returns a new position at /// the indicated edge of the containing caret unit. /// If position is located at the beginning of end of content -- there is /// no content in the indicated direction -- then this method returns /// a position located at the same location as initial position. /// internal override ITextPointer GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction) { FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; if (_GetFixedPosition(ftp, out fixedp)) { DependencyObject element = this.FixedPage.GetElement(fixedp.Node); if (element is Glyphs) { Glyphs g = (Glyphs)element; GlyphRun run = g.ToGlyphRun(); int characterCount = (run.Characters == null) ? 0 : run.Characters.Count; CharacterHit start = (fixedp.Offset == characterCount) ? new CharacterHit(fixedp.Offset - 1, 1) : new CharacterHit(fixedp.Offset, 0); CharacterHit next = (direction == LogicalDirection.Forward) ? run.GetNextCaretCharacterHit(start) : run.GetPreviousCaretCharacterHit(start); if (!start.Equals(next)) { LogicalDirection edge = LogicalDirection.Forward; if (next.TrailingLength > 0) { edge = LogicalDirection.Backward; } int index = next.FirstCharacterIndex + next.TrailingLength; return _CreateTextPointer(new FixedPosition(fixedp.Node, index), edge); } } } //default behavior is to simply move textpointer ITextPointer pointer = position.CreatePointer(); pointer.MoveToNextInsertionPosition(direction); return pointer; } ////// Finds the previous position at the edge of a caret after backspacing. /// /// /// Initial text position of an object/character. /// ////// The previous caret unit break position after backspacing. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override ITextPointer GetBackspaceCaretUnitPosition(ITextPointer position) { throw new NotImplementedException(); } ////// Returns a TextSegment that spans the line on which position is located. /// /// /// Any oriented text position on the line. /// ////// TextSegment that spans the line on which position is located. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override TextSegment GetLineRange(ITextPointer position) { #if DEBUG DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("GetLineRange {0}, {1}", (FixedTextPointer)position, position.LogicalDirection)); #endif FixedTextPointer ftp = Container.VerifyPosition(position); FixedPosition fixedp; if (!_GetFixedPosition(ftp, out fixedp)) { return new TextSegment(position, position, true); } int count = 0; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetNextLine(fixedp.Node, true, ref count); if (fixedNodes == null) { // This will happen in the case of images fixedNodes = new FixedNode[] { fixedp.Node }; } FixedNode lastNode = fixedNodes[fixedNodes.Length - 1]; DependencyObject element = FixedPage.GetElement(lastNode); int lastIndex = 1; if (element is Glyphs) { lastIndex = ((Glyphs)element).UnicodeString.Length; } ITextPointer begin = _CreateTextPointer(new FixedPosition(fixedNodes[0], 0), LogicalDirection.Forward); ITextPointer end = _CreateTextPointer(new FixedPosition(lastNode, lastIndex), LogicalDirection.Backward); if (begin.CompareTo(end) > 0) { ITextPointer temp = begin; begin = end; end = temp; } return new TextSegment(begin, end, true); } ////// Determines whenever TextView contains specified position. /// /// /// A position to test. /// ////// True if TextView contains specified text position. /// Otherwise returns false. /// ////// Throws InvalidOperationException if IsValid is false. /// If IsValid returns false, Validate method must be called before /// calling this method. /// internal override bool Contains(ITextPointer position) { FixedTextPointer tp = Container.VerifyPosition(position); return ((tp.CompareTo(this.Start) > 0 && tp.CompareTo(this.End) < 0) || (tp.CompareTo(this.Start) == 0 && (tp.LogicalDirection == LogicalDirection.Forward || this.IsContainerStart)) || (tp.CompareTo(this.End) == 0 && (tp.LogicalDirection == LogicalDirection.Backward || this.IsContainerEnd)) ); } ////// Makes sure that TextView is in a clean layout state and it is /// possible to retrieve layout related data. /// ////// If IsValid returns false, it is required to call this method /// before calling any other method on TextView. /// Validate method might be very expensive, because it may lead /// to full layout update. /// internal override bool Validate() { // Always valid. Do nothing. return true; } #endregion Internal Methods //-------------------------------------------------------------------- // // Internal Properties // //-------------------------------------------------------------------- #region Internal Properties ////// internal override UIElement RenderScope { get { Visual visual = _docPage.Visual; while (visual != null && !(visual is UIElement)) { visual = VisualTreeHelper.GetParent(visual) as Visual; } return visual as UIElement; } } ////// TextContainer that stores content. /// internal override ITextContainer TextContainer { get { return this.Container; } } ////// Determines whenever layout is in clean state and it is possible /// to retrieve layout related data. /// internal override bool IsValid { get { return true; } } ////// internal override bool RendersOwnSelection { get { return true; } } ////// /// Collection of TextSegments representing content of the TextView. /// internal override ReadOnlyCollectionTextSegments { get { if (_textSegments == null) { List list = new List (1); list.Add(new TextSegment(this.Start, this.End, true)); _textSegments = new ReadOnlyCollection (list); } return _textSegments; } } internal FixedTextPointer Start { get { if (_start == null) { FlowPosition flowStart = Container.FixedTextBuilder.GetPageStartFlowPosition(this.PageIndex); _start = new FixedTextPointer(false, LogicalDirection.Forward, flowStart); } return _start; } } internal FixedTextPointer End { get { if (_end == null) { FlowPosition flowEnd = Container.FixedTextBuilder.GetPageEndFlowPosition(this.PageIndex); _end = new FixedTextPointer(false, LogicalDirection.Backward, flowEnd); } return _end; } } #endregion Internal Properties //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ #region Private Methods // hit testing to find the inner most UIElement that was hit // as well as the containing fixed page. private bool _HitTest(Point pt, out UIElement e) { e = null; HitTestResult result = VisualTreeHelper.HitTest(this.FixedPage, pt); DependencyObject v = (result != null) ? result.VisualHit : null; while (v != null) { DependencyObjectType t = v.DependencyObjectType; if (t == UIElementType || t.IsSubclassOf(UIElementType)) { e = (UIElement) v; return true; } v = VisualTreeHelper.GetParent(v); } return false; } private void _GlyphRunHitTest(Glyphs g, double xoffset, out int charIndex, out LogicalDirection edge) { charIndex = 0; edge = LogicalDirection.Forward; GlyphRun run = g.ToGlyphRun(); bool isInside; double distance; if ((run.BidiLevel & 1) != 0) { distance = run.BaselineOrigin.X - xoffset; } else { distance = xoffset - run.BaselineOrigin.X; } CharacterHit hit = run.GetCaretCharacterHitFromDistance(distance, out isInside); charIndex = hit.FirstCharacterIndex + hit.TrailingLength; edge = (hit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward; } private ITextPointer _SnapToText(Point point) { ITextPointer itp = null; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetLine(this.PageIndex, point); if (fixedNodes != null && fixedNodes.Length > 0) { double closestDistance = Double.MaxValue; double xoffset = 0; Glyphs closestGlyphs = null; FixedNode closestNode = fixedNodes[0]; foreach (FixedNode node in fixedNodes) { Glyphs startGlyphs = this.FixedPage.GetGlyphsElement(node); GeneralTransform tranToGlyphs = this.FixedPage.TransformToDescendant(startGlyphs); Point transformedPt = point; if (tranToGlyphs != null) { tranToGlyphs.TryTransform(transformedPt, out transformedPt); } GlyphRun run = startGlyphs.ToGlyphRun(); Rect alignmentRect = run.ComputeAlignmentBox(); alignmentRect.Offset(startGlyphs.OriginX, startGlyphs.OriginY); double horizontalDistance = Math.Max(0, (transformedPt.X > alignmentRect.X) ? (transformedPt.X - alignmentRect.Right) : (alignmentRect.X - transformedPt.X)); double verticalDistance = Math.Max(0, (transformedPt.Y > alignmentRect.Y) ? (transformedPt.Y - alignmentRect.Bottom) : (alignmentRect.Y - transformedPt.Y)); double manhattanDistance = horizontalDistance + verticalDistance; if (closestGlyphs == null || manhattanDistance < closestDistance) { closestDistance = manhattanDistance; closestGlyphs = startGlyphs; closestNode = node; xoffset = transformedPt.X; } } int index; LogicalDirection dir; _GlyphRunHitTest(closestGlyphs, xoffset, out index, out dir); FixedPosition fixedp = new FixedPosition(closestNode, index); itp = _CreateTextPointer(fixedp, dir); Debug.Assert(itp != null); } else { // // That condition is only possible when there is no line in the page // if (point.Y < this.FixedPage.Height / 2) { itp = ((ITextPointer)this.Start).CreatePointer(LogicalDirection.Forward); itp.MoveToInsertionPosition(LogicalDirection.Forward); } else { itp = ((ITextPointer)this.End).CreatePointer(LogicalDirection.Backward); itp.MoveToInsertionPosition(LogicalDirection.Backward); } } return itp; } // If return false, nothing has been modified, which implies out of page boundary // The input of suggestedX is in the VisualRoot's cooridnate system private bool _GetNextLineGlyphs(ref FixedPosition fixedp, ref LogicalDirection edge, double suggestedX, LogicalDirection scanDir) { int count = 1; int pageIndex = fixedp.Page; bool moved = false; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetNextLine(fixedp.Node, (scanDir == LogicalDirection.Forward), ref count); if (fixedNodes != null && fixedNodes.Length > 0) { FixedPage page = Container.FixedDocument.SyncGetPage(pageIndex, false); // This line contains multiple Glyhs. Scan backward // util we hit the first one whose OriginX is smaller // then suggestedX; if (Double.IsInfinity(suggestedX)) { suggestedX = 0; } Point topOfPage = new Point(suggestedX, 0); Point secondPoint = new Point(suggestedX, 1000); FixedNode hitNode = fixedNodes[0]; Glyphs hitGlyphs = null; double closestDistance = Double.MaxValue; double xoffset = 0; for (int i = fixedNodes.Length - 1; i >= 0; i--) { FixedNode node = fixedNodes[i]; Glyphs g = page.GetGlyphsElement(node); if (g != null) { GeneralTransform transform = page.TransformToDescendant(g); Point pt1 = topOfPage; Point pt2 = secondPoint; if (transform != null) { transform.TryTransform(pt1, out pt1); transform.TryTransform(pt2, out pt2); } double invSlope = (pt2.X - pt1.X) / (pt2.Y - pt1.Y); double xoff, distance; GlyphRun run = g.ToGlyphRun(); Rect box = run.ComputeAlignmentBox(); box.Offset(g.OriginX, g.OriginY); if (invSlope > 1000 || invSlope < -1000) { // special case for vertical text xoff = 0; distance = (pt1.Y > box.Y) ? (pt1.Y - box.Bottom) : (box.Y - pt1.Y); } else { double centerY = (box.Top + box.Bottom) / 2; xoff = pt1.X + invSlope * (centerY - pt1.Y); distance = (xoff > box.X) ? (xoff - box.Right) : (box.X - xoff); } if (distance < closestDistance) { closestDistance = distance; xoffset = xoff; hitNode = node; hitGlyphs = g; if (distance <= 0) { break; } } } } Debug.Assert(hitGlyphs != null); int charIdx; _GlyphRunHitTest(hitGlyphs, xoffset, out charIdx, out edge); fixedp = new FixedPosition(hitNode, charIdx); moved = true; } return moved; } private static double _GetDistanceToCharacter(GlyphRun run, int charOffset) { int firstChar = charOffset, trailingLength = 0; int characterCount = (run.Characters == null) ? 0 : run.Characters.Count; if (firstChar == characterCount) { // place carat at end of previous character to make sure it works at end of line firstChar--; trailingLength = 1; } return run.GetDistanceFromCaretCharacterHit(new CharacterHit(firstChar, trailingLength)); } // char index == -1 implies end of run. internal static Rect _GetGlyphRunDesignRect(Glyphs g, int charStart, int charEnd) { GlyphRun run = g.ToGlyphRun(); if (run == null) { return Rect.Empty; } Rect designRect = run.ComputeAlignmentBox(); designRect.Offset(run.BaselineOrigin.X, run.BaselineOrigin.Y); int charCount = 0; if (run.Characters != null) { charCount = run.Characters.Count; } else if (g.UnicodeString != null) { charCount = g.UnicodeString.Length; } if (charStart > charCount) { //Extra space was added at the end of the run for contiguity Debug.Assert(charStart - charCount == 1); charStart = charCount; } else if (charStart < 0) { //This is a reversed run charStart = 0; } if (charEnd > charCount) { //Extra space was added at the end of the run for contiguity Debug.Assert(charEnd - charCount == 1); charEnd = charCount; } else if (charEnd < 0) { //This is a reversed run charEnd = 0; } double x1 = _GetDistanceToCharacter(run, charStart); double x2 = _GetDistanceToCharacter(run, charEnd); double width = x2 - x1; if ((run.BidiLevel & 1) != 0) { // right to left designRect.X = run.BaselineOrigin.X - x2; } else { designRect.X = run.BaselineOrigin.X + x1; } designRect.Width = width; return designRect; } // Creates an axis-aligned caret for possibly rotated glyphs private Rect _GetTransformedCaretRect(GeneralTransform transform, Point origin, double height) { Point bottom = origin; bottom.Y += height; transform.TryTransform(origin, out origin); transform.TryTransform(bottom, out bottom); Rect caretRect = new Rect(origin, bottom); if (caretRect.Width > 0) { // only vertical carets are supported by TextEditor // What to do if height == 0? caretRect.X += caretRect.Width / 2; caretRect.Width = 0; } if (caretRect.Height < 1) { caretRect.Height = 1; } return caretRect; } //------------------------------------------------------------------- // FlowPosition --> FixedPosition //--------------------------------------------------------------------- // Helper function to overcome the limitation in FixedTextBuilder.GetFixedPosition // Making sure we are asking a position that is either a Run or an EmbeddedElement. private bool _GetFixedPosition(FixedTextPointer ftp, out FixedPosition fixedp) { LogicalDirection textdir = ftp.LogicalDirection; TextPointerContext symbolType = ((ITextPointer)ftp).GetPointerContext(textdir); if (ftp.FlowPosition.IsBoundary || symbolType == TextPointerContext.None) { return _GetFirstFixedPosition(ftp, out fixedp); } if (symbolType == TextPointerContext.ElementStart || symbolType == TextPointerContext.ElementEnd) { //Try to find the first valid insertion position if exists switch (symbolType) { case TextPointerContext.ElementStart: textdir = LogicalDirection.Forward; break; case TextPointerContext.ElementEnd: textdir = LogicalDirection.Backward; break; } FixedTextPointer nav = new FixedTextPointer(true, textdir, (FlowPosition)ftp.FlowPosition.Clone()); _SkipFormattingTags(nav); symbolType = ((ITextPointer)nav).GetPointerContext(textdir); if (symbolType != TextPointerContext.Text && symbolType != TextPointerContext.EmbeddedElement) { //Try moving to the next insertion position if (((ITextPointer)nav).MoveToNextInsertionPosition(textdir) && this.Container.GetPageNumber(nav) == this.PageIndex) { return Container.FixedTextBuilder.GetFixedPosition(nav.FlowPosition, textdir, out fixedp); } else { fixedp = new FixedPosition(this.Container.FixedTextBuilder.FixedFlowMap.FixedStartEdge, 0); return false; } } else { ftp = nav; } } Debug.Assert(symbolType == TextPointerContext.Text || symbolType == TextPointerContext.EmbeddedElement); return Container.FixedTextBuilder.GetFixedPosition(ftp.FlowPosition, textdir, out fixedp); } //Find the first valid insertion position after or before the boundary node private bool _GetFirstFixedPosition(FixedTextPointer ftp, out FixedPosition fixedP) { LogicalDirection dir = LogicalDirection.Forward; if (ftp.FlowPosition.FlowNode.Fp != 0) { //End boundary dir = LogicalDirection.Backward; } FlowPosition flowP = (FlowPosition) ftp.FlowPosition.Clone(); //Get the first node that comes before or after the boundary node(probably a start or end node) flowP.Move(dir); FixedTextPointer nav = new FixedTextPointer(true, dir, flowP); if (flowP.IsStart || flowP.IsEnd) { ((ITextPointer)nav).MoveToNextInsertionPosition(dir); } if (this.Container.GetPageNumber(nav) == this.PageIndex) { return Container.FixedTextBuilder.GetFixedPosition(nav.FlowPosition, dir, out fixedP); } else { //This position is on another page. fixedP = new FixedPosition(this.Container.FixedTextBuilder.FixedFlowMap.FixedStartEdge, 0); return false; } } //------------------------------------------------------------------- // FixedPosition --> ITextPointer //---------------------------------------------------------------------- // Create a text position from description of a fixed position. // This is primarily called from HitTesting code private ITextPointer _CreateTextPointer(FixedPosition fixedPosition, LogicalDirection edge) { // Create a FlowPosition to represent this fixed position FlowPosition flowHit = Container.FixedTextBuilder.CreateFlowPosition(fixedPosition); if (flowHit != null) { DocumentsTrace.FixedTextOM.TextView.Trace(string.Format("_CreatetTextPointer {0}:{1}", fixedPosition.ToString(), flowHit.ToString())); // Create a TextPointer from the flow position return new FixedTextPointer(true, edge, flowHit); } return null; } // Create a text position from description of a fixed position. // This is primarily called from HitTesting code private ITextPointer _CreateTextPointerFromGlyphs(Glyphs g, Point point) { GeneralTransform transform = this.VisualRoot.TransformToDescendant(g); if (transform != null) { transform.TryTransform(point, out point); } int charIndex; LogicalDirection edge; _GlyphRunHitTest(g, point.X, out charIndex, out edge); FixedPosition fixedp = new FixedPosition(this.FixedPage.CreateFixedNode(this.PageIndex, g), charIndex); return _CreateTextPointer(fixedp, edge); } private void _SkipFormattingTags(ITextPointer textPointer) { Debug.Assert(!textPointer.IsFrozen, "Can't reposition a frozen pointer!"); LogicalDirection dir = textPointer.LogicalDirection; int increment = (dir == LogicalDirection.Forward ? +1 : -1); while (TextSchema.IsFormattingType( textPointer.GetElementType(dir)) ) { textPointer.MoveByOffset(increment); } } #endregion Private methods //----------------------------------------------------- // // Private Properties // //------------------------------------------------------ #region Private Properties private FixedTextContainer Container { get { return (FixedTextContainer)_docPage.TextContainer; } } // The visual node that is root of this TextView's visual tree private Visual VisualRoot { get { return this._docPage.Visual; } } // The FixedPage that provides content for this view private FixedPage FixedPage { get { return this._docPage.FixedPage; } } // private int PageIndex { get { return this._docPage.PageIndex; } } private bool IsContainerStart { get { return (this.Start.CompareTo(this.TextContainer.Start) == 0); } } private bool IsContainerEnd { get { return (this.End.CompareTo(this.TextContainer.End) == 0); } } #endregion Private Properties //-------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields private readonly FixedDocumentPage _docPage; // Cache for start/end private FixedTextPointer _start; private FixedTextPointer _end; private ReadOnlyCollection _textSegments; /// /// Caches the UIElement's DependencyObjectType. /// private static DependencyObjectType UIElementType = DependencyObjectType.FromSystemTypeInternal(typeof(UIElement)); #endregion Private Fields } } // 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
- AndMessageFilter.cs
- DataGridViewColumnTypeEditor.cs
- SR.cs
- FormatStringEditor.cs
- MultiView.cs
- ListItem.cs
- ModelMemberCollection.cs
- GenericsInstances.cs
- FontFamilyValueSerializer.cs
- FlowchartDesigner.xaml.cs
- DependencyObjectProvider.cs
- DefaultAssemblyResolver.cs
- PopupRoot.cs
- SecurityContextSecurityTokenResolver.cs
- UInt64.cs
- baseaxisquery.cs
- XmlSchemaNotation.cs
- SchemaImporterExtension.cs
- AudioFormatConverter.cs
- SqlCacheDependencyDatabase.cs
- BoundPropertyEntry.cs
- XmlEncoding.cs
- WindowsComboBox.cs
- XmlSchemaSubstitutionGroup.cs
- EntityModelBuildProvider.cs
- PrintController.cs
- WithParamAction.cs
- MediaPlayerState.cs
- ResourceSet.cs
- MaskInputRejectedEventArgs.cs
- InheritedPropertyChangedEventArgs.cs
- PrtCap_Reader.cs
- SerializerDescriptor.cs
- XhtmlBasicObjectListAdapter.cs
- WebPartTransformerCollection.cs
- FocusWithinProperty.cs
- RemoteWebConfigurationHost.cs
- SafeHandles.cs
- AttachedPropertiesService.cs
- SimpleMailWebEventProvider.cs
- ThreadAbortException.cs
- ProfileParameter.cs
- PropertyItem.cs
- ResourceReader.cs
- BuildManager.cs
- ApplicationManager.cs
- FunctionDescription.cs
- BlobPersonalizationState.cs
- securitycriticaldataformultiplegetandset.cs
- ValidationUtility.cs
- BadImageFormatException.cs
- MultiBindingExpression.cs
- filewebresponse.cs
- ContravarianceAdapter.cs
- ClientConfigurationHost.cs
- FindProgressChangedEventArgs.cs
- XDRSchema.cs
- BooleanKeyFrameCollection.cs
- PolyLineSegment.cs
- SafeProcessHandle.cs
- Animatable.cs
- Native.cs
- RichTextBox.cs
- HttpHandler.cs
- UrlParameterWriter.cs
- HttpApplicationFactory.cs
- DefaultHttpHandler.cs
- CacheHelper.cs
- GPRECTF.cs
- SafeLibraryHandle.cs
- CssClassPropertyAttribute.cs
- DiagnosticTrace.cs
- TypeDescriptionProvider.cs
- dsa.cs
- RoleServiceManager.cs
- NetPipeSection.cs
- Internal.cs
- MarkupObject.cs
- RepeatBehavior.cs
- RegistryKey.cs
- ContainerParagraph.cs
- StaticSiteMapProvider.cs
- AttributeParameterInfo.cs
- FixedSOMFixedBlock.cs
- SelectionProviderWrapper.cs
- DataBoundControlHelper.cs
- HitTestResult.cs
- PeerNameRecordCollection.cs
- odbcmetadatacollectionnames.cs
- RSAPKCS1SignatureDeformatter.cs
- CompensationParticipant.cs
- BitmapSource.cs
- DeclarativeCatalogPart.cs
- CryptoApi.cs
- NativeObjectSecurity.cs
- NamespaceInfo.cs
- HttpDictionary.cs
- PointAnimationClockResource.cs
- SmiEventSink_Default.cs
- ActiveXHost.cs