Free cookie consent management tool by TermsFeed Policy Generator

source: branches/ALPS/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Rendering/BackgroundGeometryBuilder.cs @ 12048

Last change on this file since 12048 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 14.6 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Linq;
22using System.Windows;
23using System.Windows.Controls.Primitives;
24using System.Windows.Media;
25using System.Windows.Media.TextFormatting;
26using ICSharpCode.NRefactory.Editor;
27using ICSharpCode.AvalonEdit.Document;
28using ICSharpCode.AvalonEdit.Editing;
29using ICSharpCode.AvalonEdit.Utils;
30
31namespace ICSharpCode.AvalonEdit.Rendering
32{
33  /// <summary>
34  /// Helper for creating a PathGeometry.
35  /// </summary>
36  public sealed class BackgroundGeometryBuilder
37  {
38    double cornerRadius;
39   
40    /// <summary>
41    /// Gets/sets the radius of the rounded corners.
42    /// </summary>
43    public double CornerRadius {
44      get { return cornerRadius; }
45      set { cornerRadius = value; }
46    }
47   
48    /// <summary>
49    /// Gets/Sets whether to align the geometry to whole pixels.
50    /// </summary>
51    public bool AlignToWholePixels { get; set; }
52   
53    /// <summary>
54    /// Gets/Sets whether to align the geometry to the middle of pixels.
55    /// </summary>
56    public bool AlignToMiddleOfPixels { get; set; }
57   
58    /// <summary>
59    /// Gets/Sets whether to extend the rectangles to full width at line end.
60    /// </summary>
61    public bool ExtendToFullWidthAtLineEnd { get; set; }
62   
63    /// <summary>
64    /// Creates a new BackgroundGeometryBuilder instance.
65    /// </summary>
66    public BackgroundGeometryBuilder()
67    {
68    }
69   
70    /// <summary>
71    /// Adds the specified segment to the geometry.
72    /// </summary>
73    public void AddSegment(TextView textView, ISegment segment)
74    {
75      if (textView == null)
76        throw new ArgumentNullException("textView");
77      Size pixelSize = PixelSnapHelpers.GetPixelSize(textView);
78      foreach (Rect r in GetRectsForSegment(textView, segment, ExtendToFullWidthAtLineEnd)) {
79        AddRectangle(pixelSize, r);
80      }
81    }
82   
83    /// <summary>
84    /// Adds a rectangle to the geometry.
85    /// </summary>
86    /// <remarks>
87    /// This overload will align the coordinates according to
88    /// <see cref="AlignToWholePixels"/> or <see cref="AlignToMiddleOfPixels"/>.
89    /// Use the <see cref="AddRectangle(double,double,double,double)"/>-overload instead if the coordinates should not be aligned.
90    /// </remarks>
91    public void AddRectangle(TextView textView, Rect rectangle)
92    {
93      AddRectangle(PixelSnapHelpers.GetPixelSize(textView), rectangle);
94    }
95
96    void AddRectangle(Size pixelSize, Rect r)
97    {
98      if (AlignToWholePixels) {
99        AddRectangle(PixelSnapHelpers.Round(r.Left, pixelSize.Width),
100                     PixelSnapHelpers.Round(r.Top + 1, pixelSize.Height),
101                     PixelSnapHelpers.Round(r.Right, pixelSize.Width),
102                     PixelSnapHelpers.Round(r.Bottom + 1, pixelSize.Height));
103      } else if (AlignToMiddleOfPixels) {
104        AddRectangle(PixelSnapHelpers.PixelAlign(r.Left, pixelSize.Width),
105                     PixelSnapHelpers.PixelAlign(r.Top + 1, pixelSize.Height),
106                     PixelSnapHelpers.PixelAlign(r.Right, pixelSize.Width),
107                     PixelSnapHelpers.PixelAlign(r.Bottom + 1, pixelSize.Height));
108      } else {
109        AddRectangle(r.Left, r.Top + 1, r.Right, r.Bottom + 1);
110      }
111    }
112   
113    /// <summary>
114    /// Calculates the list of rectangle where the segment in shown.
115    /// This method usually returns one rectangle for each line inside the segment
116    /// (but potentially more, e.g. when bidirectional text is involved).
117    /// </summary>
118    public static IEnumerable<Rect> GetRectsForSegment(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd = false)
119    {
120      if (textView == null)
121        throw new ArgumentNullException("textView");
122      if (segment == null)
123        throw new ArgumentNullException("segment");
124      return GetRectsForSegmentImpl(textView, segment, extendToFullWidthAtLineEnd);
125    }
126   
127    static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd)
128    {
129      int segmentStart = segment.Offset;
130      int segmentEnd = segment.Offset + segment.Length;
131     
132      segmentStart = segmentStart.CoerceValue(0, textView.Document.TextLength);
133      segmentEnd = segmentEnd.CoerceValue(0, textView.Document.TextLength);
134     
135      TextViewPosition start;
136      TextViewPosition end;
137     
138      if (segment is SelectionSegment) {
139        SelectionSegment sel = (SelectionSegment)segment;
140        start = new TextViewPosition(textView.Document.GetLocation(sel.StartOffset), sel.StartVisualColumn);
141        end = new TextViewPosition(textView.Document.GetLocation(sel.EndOffset), sel.EndVisualColumn);
142      } else {
143        start = new TextViewPosition(textView.Document.GetLocation(segmentStart));
144        end = new TextViewPosition(textView.Document.GetLocation(segmentEnd));
145      }
146     
147      foreach (VisualLine vl in textView.VisualLines) {
148        int vlStartOffset = vl.FirstDocumentLine.Offset;
149        if (vlStartOffset > segmentEnd)
150          break;
151        int vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length;
152        if (vlEndOffset < segmentStart)
153          continue;
154       
155        int segmentStartVC;
156        if (segmentStart < vlStartOffset)
157          segmentStartVC = 0;
158        else
159          segmentStartVC = vl.ValidateVisualColumn(start, extendToFullWidthAtLineEnd);
160       
161        int segmentEndVC;
162        if (segmentEnd > vlEndOffset)
163          segmentEndVC = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker;
164        else
165          segmentEndVC = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd);
166       
167        foreach (var rect in ProcessTextLines(textView, vl, segmentStartVC, segmentEndVC))
168          yield return rect;
169      }
170    }
171   
172    /// <summary>
173    /// Calculates the rectangles for the visual column segment.
174    /// This returns one rectangle for each line inside the segment.
175    /// </summary>
176    public static IEnumerable<Rect> GetRectsFromVisualSegment(TextView textView, VisualLine line, int startVC, int endVC)
177    {
178      if (textView == null)
179        throw new ArgumentNullException("textView");
180      if (line == null)
181        throw new ArgumentNullException("line");
182      return ProcessTextLines(textView, line, startVC, endVC);
183    }
184
185    static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVC, int segmentEndVC)
186    {
187      TextLine lastTextLine = visualLine.TextLines.Last();
188      Vector scrollOffset = textView.ScrollOffset;
189     
190      for (int i = 0; i < visualLine.TextLines.Count; i++) {
191        TextLine line = visualLine.TextLines[i];
192        double y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop);
193        int visualStartCol = visualLine.GetTextLineVisualStartColumn(line);
194        int visualEndCol = visualStartCol + line.Length;
195        if (line != lastTextLine)
196          visualEndCol -= line.TrailingWhitespaceLength;
197       
198        if (segmentEndVC < visualStartCol)
199          break;
200        if (lastTextLine != line && segmentStartVC > visualEndCol)
201          continue;
202        int segmentStartVCInLine = Math.Max(segmentStartVC, visualStartCol);
203        int segmentEndVCInLine = Math.Min(segmentEndVC, visualEndCol);
204        y -= scrollOffset.Y;
205        if (segmentStartVCInLine == segmentEndVCInLine) {
206          // GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit
207          // We need to return a rectangle to ensure empty lines are still visible
208          double pos = visualLine.GetTextLineVisualXPosition(line, segmentStartVCInLine);
209          pos -= scrollOffset.X;
210          // The following special cases are necessary to get rid of empty rectangles at the end of a TextLine if "Show Spaces" is active.
211          // If not excluded once, the same rectangle is calculated (and added) twice (since the offset could be mapped to two visual positions; end/start of line), if there is no trailing whitespace.
212          // Skip this TextLine segment, if it is at the end of this line and this line is not the last line of the VisualLine and the selection continues and there is no trailing whitespace.
213          if (segmentEndVCInLine == visualEndCol && i < visualLine.TextLines.Count - 1 && segmentEndVC > segmentEndVCInLine && line.TrailingWhitespaceLength == 0)
214            continue;
215          if (segmentStartVCInLine == visualStartCol && i > 0 && segmentStartVC < segmentStartVCInLine && visualLine.TextLines[i - 1].TrailingWhitespaceLength == 0)
216            continue;
217          yield return new Rect(pos, y, 1, line.Height);
218        } else {
219          Rect lastRect = Rect.Empty;
220          if (segmentStartVCInLine <= visualEndCol) {
221            foreach (TextBounds b in line.GetTextBounds(segmentStartVCInLine, segmentEndVCInLine - segmentStartVCInLine)) {
222              double left = b.Rectangle.Left - scrollOffset.X;
223              double right = b.Rectangle.Right - scrollOffset.X;
224              if (!lastRect.IsEmpty)
225                yield return lastRect;
226              // left>right is possible in RTL languages
227              lastRect = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
228            }
229          }
230          if (segmentEndVC >= visualLine.VisualLengthWithEndOfLineMarker) {
231            double left = (segmentStartVC > visualLine.VisualLengthWithEndOfLineMarker ? visualLine.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X;
232            double right = ((segmentEndVC == int.MaxValue || line != lastTextLine) ? Math.Max(((IScrollInfo)textView).ExtentWidth, ((IScrollInfo)textView).ViewportWidth) : visualLine.GetTextLineVisualXPosition(lastTextLine, segmentEndVC)) - scrollOffset.X;
233            Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
234            if (!lastRect.IsEmpty) {
235              if (extendSelection.IntersectsWith(lastRect)) {
236                lastRect.Union(extendSelection);
237                yield return lastRect;
238              } else {
239                yield return lastRect;
240                yield return extendSelection;
241              }
242            } else
243              yield return extendSelection;
244          } else
245            yield return lastRect;
246        }
247      }
248    }
249   
250    PathFigureCollection figures = new PathFigureCollection();
251    PathFigure figure;
252    int insertionIndex;
253    double lastTop, lastBottom;
254    double lastLeft, lastRight;
255   
256    /// <summary>
257    /// Adds a rectangle to the geometry.
258    /// </summary>
259    /// <remarks>
260    /// This overload assumes that the coordinates are aligned properly
261    /// (see <see cref="AlignToWholePixels"/>, <see cref="AlignToMiddleOfPixels"/>).
262    /// Use the <see cref="AddRectangle(TextView,Rect)"/>-overload instead if the coordinates are not yet aligned.
263    /// </remarks>
264    public void AddRectangle(double left, double top, double right, double bottom)
265    {
266      if (!top.IsClose(lastBottom)) {
267        CloseFigure();
268      }
269      if (figure == null) {
270        figure = new PathFigure();
271        figure.StartPoint = new Point(left, top + cornerRadius);
272        if (Math.Abs(left - right) > cornerRadius) {
273          figure.Segments.Add(MakeArc(left + cornerRadius, top, SweepDirection.Clockwise));
274          figure.Segments.Add(MakeLineSegment(right - cornerRadius, top));
275          figure.Segments.Add(MakeArc(right, top + cornerRadius, SweepDirection.Clockwise));
276        }
277        figure.Segments.Add(MakeLineSegment(right, bottom - cornerRadius));
278        insertionIndex = figure.Segments.Count;
279        //figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise));
280      } else {
281        if (!lastRight.IsClose(right)) {
282          double cr = right < lastRight ? -cornerRadius : cornerRadius;
283          SweepDirection dir1 = right < lastRight ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
284          SweepDirection dir2 = right < lastRight ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
285          figure.Segments.Insert(insertionIndex++, MakeArc(lastRight + cr, lastBottom, dir1));
286          figure.Segments.Insert(insertionIndex++, MakeLineSegment(right - cr, top));
287          figure.Segments.Insert(insertionIndex++, MakeArc(right, top + cornerRadius, dir2));
288        }
289        figure.Segments.Insert(insertionIndex++, MakeLineSegment(right, bottom - cornerRadius));
290        figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft, lastTop + cornerRadius));
291        if (!lastLeft.IsClose(left)) {
292          double cr = left < lastLeft ? cornerRadius : -cornerRadius;
293          SweepDirection dir1 = left < lastLeft ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
294          SweepDirection dir2 = left < lastLeft ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
295          figure.Segments.Insert(insertionIndex, MakeArc(lastLeft, lastBottom - cornerRadius, dir1));
296          figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft - cr, lastBottom));
297          figure.Segments.Insert(insertionIndex, MakeArc(left + cr, lastBottom, dir2));
298        }
299      }
300      this.lastTop = top;
301      this.lastBottom = bottom;
302      this.lastLeft = left;
303      this.lastRight = right;
304    }
305   
306    ArcSegment MakeArc(double x, double y, SweepDirection dir)
307    {
308      ArcSegment arc = new ArcSegment(
309        new Point(x, y),
310        new Size(cornerRadius, cornerRadius),
311        0, false, dir, true);
312      arc.Freeze();
313      return arc;
314    }
315   
316    static LineSegment MakeLineSegment(double x, double y)
317    {
318      LineSegment ls = new LineSegment(new Point(x, y), true);
319      ls.Freeze();
320      return ls;
321    }
322   
323    /// <summary>
324    /// Closes the current figure.
325    /// </summary>
326    public void CloseFigure()
327    {
328      if (figure != null) {
329        figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft, lastTop + cornerRadius));
330        if (Math.Abs(lastLeft - lastRight) > cornerRadius) {
331          figure.Segments.Insert(insertionIndex, MakeArc(lastLeft, lastBottom - cornerRadius, SweepDirection.Clockwise));
332          figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft + cornerRadius, lastBottom));
333          figure.Segments.Insert(insertionIndex, MakeArc(lastRight - cornerRadius, lastBottom, SweepDirection.Clockwise));
334        }
335       
336        figure.IsClosed = true;
337        figures.Add(figure);
338        figure = null;
339      }
340    }
341   
342    /// <summary>
343    /// Creates the geometry.
344    /// Returns null when the geometry is empty!
345    /// </summary>
346    public Geometry CreateGeometry()
347    {
348      CloseFigure();
349      if (figures.Count != 0) {
350        PathGeometry g = new PathGeometry(figures);
351        g.Freeze();
352        return g;
353      } else {
354        return null;
355      }
356    }
357  }
358}
Note: See TracBrowser for help on using the repository browser.