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.
		
		
		
		
		
			
		
			
				
					
					
						
							265 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							265 lines
						
					
					
						
							13 KiB
						
					
					
				<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | 
						|
<!------------------------------------------------------------> | 
						|
<!--                           INTRODUCTION                                 | 
						|
 | 
						|
 The Code Project article submission template (HTML version) | 
						|
 | 
						|
Using this template will help us post your article sooner. To use, just  | 
						|
follow the 3 easy steps below: | 
						|
  | 
						|
     1. Fill in the article description details | 
						|
     2. Add links to your images and downloads | 
						|
     3. Include the main article text | 
						|
 | 
						|
That's all there is to it! All formatting will be done by our submission | 
						|
scripts and style sheets.  | 
						|
 | 
						|
-->   | 
						|
<!------------------------------------------------------------> | 
						|
<!--                        IGNORE THIS SECTION                            --> | 
						|
<html> | 
						|
<head> | 
						|
<title>Rendering</title> | 
						|
<Style> | 
						|
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt } | 
						|
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; } | 
						|
H2 { font-size: 13pt; } | 
						|
H3 { font-size: 12pt; } | 
						|
H4 { font-size: 10pt; color: black; } | 
						|
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; } | 
						|
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; } | 
						|
</style> | 
						|
<link rel="stylesheet" type="text/css" href="http://www.codeproject.com/App_Themes/NetCommunity/CodeProject.css"> | 
						|
</head> | 
						|
<body bgcolor="#FFFFFF" color=#000000> | 
						|
<div style="width:600px; margin-left: 24px;"> | 
						|
<!------------------------------------------------------------> | 
						|
 | 
						|
 | 
						|
<!------------------------------------------------------------> | 
						|
<!--  Fill in the details (CodeProject will reformat this section for you) --> | 
						|
 | 
						|
 | 
						|
<!------------------------------------------------------------> | 
						|
<!--  Include download and sample image information.                       -->  | 
						|
 | 
						|
For the sample application and source code download, please see the main article: | 
						|
<a href="http://www.codeproject.com/KB/edit/AvalonEdit.aspx">Using AvalonEdit (WPF Text Editor)</a> | 
						|
 | 
						|
<p><img src="Article.gif" alt="Sample Image - maximum width is 600 pixels" width=400 height=200></p> | 
						|
 | 
						|
 | 
						|
<!------------------------------------------------------------> | 
						|
 | 
						|
<!--  Add the article text. Please use simple formatting (<h2>, <p> etc)   -->  | 
						|
 | 
						|
<h2>Introduction</h2> | 
						|
 | 
						|
<h2>Using the Code</h2> | 
						|
 | 
						|
 | 
						|
<!------------------------------------------------------------> | 
						|
<h2>Rendering</h2> | 
						|
 | 
						|
Noticed how through the whole 'Document' section, there was no mention of extensibility? | 
						|
The text rendering infrastructure now has to compensate for that by being completely extensible. | 
						|
 | 
						|
<p>The <code>ICSharpCode.AvalonEdit.Rendering.TextView</code> class is the heart of AvalonEdit. | 
						|
It takes care of getting the document onto the screen. | 
						|
 | 
						|
<p>To do this in an extensible way, the <code>TextView</code> uses its own kind of model: the <code>VisualLine</code>. | 
						|
Visual lines are created only for the visible part of the document. | 
						|
<p>The rendering process looks like this:<br> | 
						|
<img src="AvalonEdit/renderingPipeline.png" alt="rendering pipeline"><br> | 
						|
The last step in the pipeline is the conversion to one or more <code>System.Windows.Media.TextFormatting.TextLine</code> instances. WPF then takes care of the actual text rendering. | 
						|
 | 
						|
<h3>Lifetime of visual lines</h3> | 
						|
When the <code>TextView</code> needs to construct visual lines (usually before rendering), it first | 
						|
determines which <code>DocumentLine</code> is the top-most visible line in the currently viewed region. | 
						|
From there, it starts to build visual lines and also immediately does the conversion to <code>TextLine</code> (word-wrapping). | 
						|
The process stops once the viewed document region is filled. | 
						|
<p> | 
						|
The resulting visual lines (and <code>TextLine</code>s) will be cached and reused in future rendering passes. | 
						|
When the user scrolls down, only the visual lines coming into view are created, the rest is reused. | 
						|
<p> | 
						|
The <code>TextView.Redraw</code> methods are used to remove visual lines from the cache. | 
						|
AvalonEdit will redraw automatically on the affected lines when the document is changed; and will invalidate the whole cache | 
						|
when any editor options are changed. You will only have to call <code>Redraw</code> manually if you write extensions to the visual line creation process | 
						|
that maintain their own data source. For example, the <code>FoldingManager</code> invokes <code>Redraw</code> whenever text sections are expanded or collapsed. | 
						|
<p> | 
						|
Calling <code>Redraw</code> does not cause immediate recreation of the lines. | 
						|
They are just removed from the cache so that the next rendering step will recreate them. | 
						|
All redraw methods will enqueue a new rendering step, using the WPF Dispatcher with a low priority. | 
						|
 | 
						|
<h3>Elements inside visual line</h3> | 
						|
A visual line consists of a series of elements. These have both a <code>DocumentLength</code> measured in characters as well as a logical length called <code>VisualLength</code>. | 
						|
For normal text elements, the two lengths are identical; but some elements like fold markers may have a huge document length, yet a logical length of 1. | 
						|
On the other hand, some elements that are simply inserted by element generators may have a document length of 0, but still need a logical length of at least 1 to allow | 
						|
addressing elements inside the visual line. | 
						|
<p> | 
						|
The <code>VisualColumn</code> is a position inside a visual line as measured by the logical length of elements. It is counted starting from 0 at the begin of the visual line.<br> | 
						|
Also, inside visual lines, instead of normal offsets to the text document; relative offsets are used.<br> | 
						|
<code>Absolute offset = relative offset + VisualLine.FirstDocumentLine.Offset</code><br> | 
						|
This means that offsets inside the visual line do not have to be adjusted when text is inserted or removed in front of the visual line; we simply rely on the document | 
						|
automatically updating <code>DocumentLine.Offset</code>. | 
						|
<p> | 
						|
The main job of a visual line element is to implement the <code>CreateTextRun</code> method. | 
						|
This method should return a <code>System.Windows.Media.TextFormatting.TextRun</code> instance that can be rendered using the <code>TextLine</code> class. | 
						|
<p> | 
						|
Visual line elements can also handle mouse clicks and control how the caret should move. The mouse click handling might suffice as a light-weight alternative | 
						|
to embedding inline <code>UIElement</code>s in the visual lines. | 
						|
 | 
						|
<h3>Element Generators</h3> | 
						|
 | 
						|
You can extend the text view by registering a custom class deriving from <code>VisualLineElementGenerator</code> in the <code>TextView.ElementGenerators</code> collection. | 
						|
This allows you to add custom <code>VisualLineElements</code>. | 
						|
Using the <code>InlineObjectElement</code> class, you can even put interactive WPF controls (anything derived from <code>UIElement</code>) into the text document. | 
						|
<p> | 
						|
For all document text not consumed by element generators, AvalonEdit will create <code>VisualLineText</code> elements. | 
						|
<p> | 
						|
Usually, the construction of the visual line will stop at the end of the <code>DocumentLine</code>. However, if some <code>VisualLineElementGenerator</code> | 
						|
creates an element that's longer than the rest of the line, construction of the visual line may resume in another <code>DocumentLine</code>. | 
						|
Currently, only the <code>FoldingElementGenerator</code> can cause one visual line to span multiple <code>DocumentLine</code>s. | 
						|
<p> | 
						|
<img src="AvalonEdit/folding.png" alt="Screenshot Folding and ImageElementGenerator"> | 
						|
<p> | 
						|
Here is the full source code for a class that implements embedding images into AvalonEdit: | 
						|
<pre lang="cs">public class ImageElementGenerator : VisualLineElementGenerator | 
						|
{ | 
						|
    readonly static Regex imageRegex = new Regex(@"<img src=""([\.\/\w\d]+)""/?>", | 
						|
                                                 RegexOptions.IgnoreCase); | 
						|
    readonly string basePath; | 
						|
     | 
						|
    public ImageElementGenerator(string basePath) | 
						|
    { | 
						|
        if (basePath == null) | 
						|
            throw new ArgumentNullException("basePath"); | 
						|
        this.basePath = basePath; | 
						|
    } | 
						|
     | 
						|
    Match FindMatch(int startOffset) | 
						|
    { | 
						|
        // fetch the end offset of the VisualLine being generated | 
						|
        int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; | 
						|
        TextDocument document = CurrentContext.Document; | 
						|
        string relevantText = document.GetText(startOffset, endOffset - startOffset); | 
						|
        return imageRegex.Match(relevantText); | 
						|
    } | 
						|
     | 
						|
    /// Gets the first offset >= startOffset where the generator wants to construct | 
						|
    /// an element. | 
						|
    /// Return -1 to signal no interest. | 
						|
    public override int GetFirstInterestedOffset(int startOffset) | 
						|
    { | 
						|
        Match m = FindMatch(startOffset); | 
						|
        return m.Success ? (startOffset + m.Index) : -1; | 
						|
    } | 
						|
     | 
						|
    /// Constructs an element at the specified offset. | 
						|
    /// May return null if no element should be constructed. | 
						|
    public override VisualLineElement ConstructElement(int offset) | 
						|
    { | 
						|
        Match m = FindMatch(offset); | 
						|
        // check whether there's a match exactly at offset | 
						|
        if (m.Success && m.Index == 0) { | 
						|
            BitmapImage bitmap = LoadBitmap(m.Groups[1].Value); | 
						|
            if (bitmap != null) { | 
						|
                Image image = new Image(); | 
						|
                image.Source = bitmap; | 
						|
                image.Width = bitmap.PixelWidth; | 
						|
                image.Height = bitmap.PixelHeight; | 
						|
                // Pass the length of the match to the 'documentLength' parameter | 
						|
                // of InlineObjectElement. | 
						|
                return new InlineObjectElement(m.Length, image); | 
						|
            } | 
						|
        } | 
						|
        return null; | 
						|
    } | 
						|
     | 
						|
    BitmapImage LoadBitmap(string fileName) | 
						|
    { | 
						|
        // TODO: add some kind of cache to avoid reloading the image whenever the | 
						|
        // VisualLine is reconstructed | 
						|
        try { | 
						|
            string fullFileName = Path.Combine(basePath, fileName); | 
						|
            if (File.Exists(fullFileName)) { | 
						|
                BitmapImage bitmap = new BitmapImage(new Uri(fullFileName)); | 
						|
                bitmap.Freeze(); | 
						|
                return bitmap; | 
						|
            } | 
						|
        } catch (ArgumentException) { | 
						|
            // invalid filename syntax | 
						|
        } catch (IOException) { | 
						|
            // other IO error | 
						|
        } | 
						|
        return null; | 
						|
    } | 
						|
}</pre> | 
						|
 | 
						|
<h3>Line Transformers</h3> | 
						|
 | 
						|
Line transformers can modify the visual lines after they have been generated. The main usage of this is to colorize the text, | 
						|
as done both by syntax highlighting and the selection. | 
						|
<p> | 
						|
The base classes <code>ColorizingTransformer</code> and <code>DocumentColorizingTransformer</code> help with this task | 
						|
by providing helper methods for colorizing that split up visual line elements where necessary. The difference between | 
						|
the two classes is that one works using visual columns whereas the other one uses offsets into the document. | 
						|
<p> | 
						|
Here is an example <code>DocumentColorizingTransformer</code> that highlights the word 'AvalonEdit' using bold font: | 
						|
<pre lang="cs">public class ColorizeAvalonEdit : DocumentColorizingTransformer | 
						|
{ | 
						|
    protected override void ColorizeLine(DocumentLine line) | 
						|
    { | 
						|
        int lineStartOffset = line.Offset; | 
						|
        string text = CurrentContext.Document.GetText(line); | 
						|
        int start = 0; | 
						|
        int index; | 
						|
        while ((index = text.IndexOf("AvalonEdit", start)) >= 0) { | 
						|
            base.ChangeLinePart( | 
						|
                lineStartOffset + index, // startOffset | 
						|
                lineStartOffset + index + 10, // endOffset | 
						|
                (VisualLineElement element) => { | 
						|
                    // This lambda gets called once for every VisualLineElement | 
						|
                    // between the specified offsets. | 
						|
                    Typeface tf = element.TextRunProperties.Typeface; | 
						|
                    // Replace the typeface with a modified version of | 
						|
                    // the same typeface | 
						|
                    element.TextRunProperties.SetTypeface(new Typeface( | 
						|
                        tf.FontFamily, | 
						|
                        FontStyles.Italic, | 
						|
                        FontWeights.Bold, | 
						|
                        tf.Stretch | 
						|
                    )); | 
						|
                }); | 
						|
            start = index + 1; // search for next occurrence | 
						|
}   }   }</pre> | 
						|
 | 
						|
<h3>Background renderers</h3> | 
						|
 | 
						|
Background renderers are simple objects that allow you to draw anything in the text view. | 
						|
They can be used to draw nice-looking backgrounds behind the text. | 
						|
<p> | 
						|
AvalonEdit contains the class <code>BackgroundGeometryBuilder</code> that helps with this task. | 
						|
You can use the static <code>BackgroundGeometryBuilder.GetRectsForSegment</code> to fetch a list of rectangles that | 
						|
contain text from the specified segment (you will get one rectangle per <code>TextLine</code>); | 
						|
or you can use the instance methods to build a <code>PathGeometry</code> for the text's outline. | 
						|
AvalonEdit also internally uses this geometry builder to create the selection with the rounded corners. | 
						|
<p> | 
						|
Inside SharpDevelop, the first option (getting list of rectangles) is used to render the squiggly red line that for compiler errors, | 
						|
while the second option is used to produce nice-looking breakpoint markers. | 
						|
 | 
						|
<h2>Points of Interest</h2> | 
						|
 | 
						|
<p>Did you learn anything interesting/fun/annoying while writing the code? Did you | 
						|
do anything particularly clever or wild or zany? | 
						|
 | 
						|
<h2>History</h2> | 
						|
 | 
						|
<p>Keep a running update of any changes or improvements you've made here. | 
						|
 | 
						|
<p><b>Note: although my sample code is provided under the MIT license, ICSharpCode.AvalonEdit itself is provided under the terms of the GNU LGPL.</b> | 
						|
 | 
						|
<!-------------------------------    That's it!   ---------------------------> | 
						|
</div></body> | 
						|
 | 
						|
</html>
 | 
						|
 |