#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

254 lines
8.9 KiB

// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Rendering
{
/// <summary>
/// Helper for creating a PathGeometry.
/// </summary>
public sealed class BackgroundGeometryBuilder
{
double cornerRadius;
/// <summary>
/// Gets/sets the radius of the rounded corners.
/// </summary>
public double CornerRadius {
get { return cornerRadius; }
set { cornerRadius = value; }
}
/// <summary>
/// Gets/Sets whether to align the geometry to whole pixels.
/// </summary>
public bool AlignToWholePixels { get; set; }
/// <summary>
/// Gets/Sets whether to align the geometry to the middle of pixels.
/// </summary>
public bool AlignToMiddleOfPixels { get; set; }
/// <summary>
/// Creates a new BackgroundGeometryBuilder instance.
/// </summary>
public BackgroundGeometryBuilder()
{
}
/// <summary>
/// Adds the specified segment to the geometry.
/// </summary>
public void AddSegment(TextView textView, ISegment segment)
{
if (textView == null)
throw new ArgumentNullException("textView");
Size pixelSize = PixelSnapHelpers.GetPixelSize(textView);
foreach (Rect r in GetRectsForSegment(textView, segment)) {
if (AlignToWholePixels) {
AddRectangle(PixelSnapHelpers.Round(r.Left, pixelSize.Width),
PixelSnapHelpers.Round(r.Top, pixelSize.Height),
PixelSnapHelpers.Round(r.Right, pixelSize.Width),
PixelSnapHelpers.Round(r.Bottom, pixelSize.Height));
} else if (AlignToMiddleOfPixels) {
AddRectangle(PixelSnapHelpers.PixelAlign(r.Left, pixelSize.Width),
PixelSnapHelpers.PixelAlign(r.Top, pixelSize.Height),
PixelSnapHelpers.PixelAlign(r.Right, pixelSize.Width),
PixelSnapHelpers.PixelAlign(r.Bottom, pixelSize.Height));
} else {
AddRectangle(r.Left, r.Top, r.Right, r.Bottom);
}
}
}
/// <summary>
/// Calculates the list of rectangle where the segment in shown.
/// This returns one rectangle for each line inside the segment.
/// </summary>
public static IEnumerable<Rect> GetRectsForSegment(TextView textView, ISegment segment)
{
if (textView == null)
throw new ArgumentNullException("textView");
if (segment == null)
throw new ArgumentNullException("segment");
return GetRectsForSegmentImpl(textView, segment);
}
static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segment)
{
Vector scrollOffset = textView.ScrollOffset;
int segmentStart = segment.Offset;
int segmentEnd = segment.Offset + segment.Length;
foreach (VisualLine vl in textView.VisualLines) {
int vlStartOffset = vl.FirstDocumentLine.Offset;
if (vlStartOffset > segmentEnd)
break;
int vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length;
if (vlEndOffset < segmentStart)
continue;
int segmentStartVC;
if (segmentStart < vlStartOffset)
segmentStartVC = 0;
else
segmentStartVC = vl.GetVisualColumn(segmentStart - vlStartOffset);
int segmentEndVC;
if (segmentEnd > vlEndOffset)
segmentEndVC = vl.VisualLength;
else
segmentEndVC = vl.GetVisualColumn(segmentEnd - vlStartOffset);
TextLine lastTextLine = vl.TextLines.Last();
foreach (TextLine line in vl.TextLines) {
double y = vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop);
int visualStartCol = vl.GetTextLineVisualStartColumn(line);
int visualEndCol = visualStartCol + line.Length;
if (line != lastTextLine)
visualEndCol -= line.TrailingWhitespaceLength;
if (segmentEndVC < visualStartCol)
break;
if (segmentStartVC > visualEndCol)
continue;
int segmentStartVCInLine = Math.Max(segmentStartVC, visualStartCol);
int segmentEndVCInLine = Math.Min(segmentEndVC, visualEndCol);
y -= scrollOffset.Y;
if (segmentStartVCInLine == segmentEndVCInLine) {
// GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit
// We need to return a rectangle to ensure empty lines are still visible
double pos = line.GetDistanceFromCharacterHit(new CharacterHit(segmentStartVCInLine, 0));
pos -= scrollOffset.X;
yield return new Rect(pos, y, 1, line.Height);
} else {
foreach (TextBounds b in line.GetTextBounds(segmentStartVCInLine, segmentEndVCInLine - segmentStartVCInLine)) {
double left = b.Rectangle.Left;
double right = b.Rectangle.Right;
left -= scrollOffset.X;
right -= scrollOffset.X;
// left>right is possible in RTL languages
yield return new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
}
}
}
}
}
PathFigureCollection figures = new PathFigureCollection();
PathFigure figure;
int insertionIndex;
double lastTop, lastBottom;
double lastLeft, lastRight;
/// <summary>
/// Adds a rectangle to the geometry.
/// </summary>
public void AddRectangle(double left, double top, double right, double bottom)
{
if (!top.IsClose(lastBottom)) {
CloseFigure();
}
if (figure == null) {
figure = new PathFigure();
figure.StartPoint = new Point(left, top + cornerRadius);
if (Math.Abs(left - right) > cornerRadius) {
figure.Segments.Add(MakeArc(left + cornerRadius, top, SweepDirection.Clockwise));
figure.Segments.Add(MakeLineSegment(right - cornerRadius, top));
figure.Segments.Add(MakeArc(right, top + cornerRadius, SweepDirection.Clockwise));
}
figure.Segments.Add(MakeLineSegment(right, bottom - cornerRadius));
insertionIndex = figure.Segments.Count;
//figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise));
} else {
if (!lastRight.IsClose(right)) {
double cr = right < lastRight ? -cornerRadius : cornerRadius;
SweepDirection dir1 = right < lastRight ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
SweepDirection dir2 = right < lastRight ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
figure.Segments.Insert(insertionIndex++, MakeArc(lastRight + cr, lastBottom, dir1));
figure.Segments.Insert(insertionIndex++, MakeLineSegment(right - cr, top));
figure.Segments.Insert(insertionIndex++, MakeArc(right, top + cornerRadius, dir2));
}
figure.Segments.Insert(insertionIndex++, MakeLineSegment(right, bottom - cornerRadius));
figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft, lastTop + cornerRadius));
if (!lastLeft.IsClose(left)) {
double cr = left < lastLeft ? cornerRadius : -cornerRadius;
SweepDirection dir1 = left < lastLeft ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
SweepDirection dir2 = left < lastLeft ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
figure.Segments.Insert(insertionIndex, MakeArc(lastLeft, lastBottom - cornerRadius, dir1));
figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft - cr, lastBottom));
figure.Segments.Insert(insertionIndex, MakeArc(left + cr, lastBottom, dir2));
}
}
this.lastTop = top;
this.lastBottom = bottom;
this.lastLeft = left;
this.lastRight = right;
}
ArcSegment MakeArc(double x, double y, SweepDirection dir)
{
ArcSegment arc = new ArcSegment(
new Point(x, y),
new Size(cornerRadius, cornerRadius),
0, false, dir, true);
arc.Freeze();
return arc;
}
static LineSegment MakeLineSegment(double x, double y)
{
LineSegment ls = new LineSegment(new Point(x, y), true);
ls.Freeze();
return ls;
}
/// <summary>
/// Closes the current figure.
/// </summary>
public void CloseFigure()
{
if (figure != null) {
figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft, lastTop + cornerRadius));
if (Math.Abs(lastLeft - lastRight) > cornerRadius) {
figure.Segments.Insert(insertionIndex, MakeArc(lastLeft, lastBottom - cornerRadius, SweepDirection.Clockwise));
figure.Segments.Insert(insertionIndex, MakeLineSegment(lastLeft + cornerRadius, lastBottom));
figure.Segments.Insert(insertionIndex, MakeArc(lastRight - cornerRadius, lastBottom, SweepDirection.Clockwise));
}
figure.IsClosed = true;
figures.Add(figure);
figure = null;
}
}
/// <summary>
/// Creates the geometry.
/// Returns null when the geometry is empty!
/// </summary>
public Geometry CreateGeometry()
{
CloseFigure();
if (figures.Count != 0) {
PathGeometry g = new PathGeometry(figures);
g.Freeze();
return g;
} else {
return null;
}
}
}
}