Advanced Text Formatting

Windows Presentation Foundation (WPF) provides a robust set of APIs for including text in your application. Layout and user interface (UI) APIs, such as TextBlock, provide the most common and general-use elements for text presentation. Drawing APIs, such as GlyphRunDrawing and FormattedText, provide a means for including formatted text in drawings. At the most advanced level, WPF provides an extensible text formatting engine to control every aspect of text presentation, such as text store management, text run formatting management, and embedded object management.

This topic provides an introduction to WPF text formatting. It focuses on client implementation and use of the WPF text formatting engine.

Note

All code examples within this document can be found in the Advanced Text Formatting Sample.

Prerequisites

This topic assumes that you are familiar with the higher level APIs used for text presentation. Most user scenarios will not require the advanced text formatting APIs discussed in this topic. For an introduction to the different text APIs, see Documents in WPF.

Advanced Text Formatting

The text layout and UI controls in WPF provide formatting properties that allow you to easily include formatted text in your application. These controls expose a number of properties to handle the presentation of text, which includes its typeface, size, and color. Under ordinary circumstances, these controls can handle the majority of text presentation in your application. However, some advanced scenarios require the control of text storage as well as text presentation. WPF provides an extensible text formatting engine for this purpose.

The advanced text formatting features found in WPF consist of a text formatting engine, a text store, text runs, and formatting properties. The text formatting engine, TextFormatter, creates lines of text to be used for presentation. This is achieved by initiating the line formatting process and calling the text formatter's FormatLine. The text formatter retrieves text runs from your text store by calling the store's GetTextRun method. The TextRun objects are then formed into TextLine objects by the text formatter and given to your application for inspection or display.

Using the Text Formatter

TextFormatter is the WPF text formatting engine and provides services for formatting and breaking text lines. The text formatter can handle different text character formats and paragraph styles, and includes support for international text layout.

Unlike a traditional text API, the TextFormatter interacts with a text layout client through a set of callback methods. It requires the client to provide these methods in an implementation of the TextSource class. The following diagram illustrates the text layout interaction between the client application and TextFormatter.

Diagram of text layout client and TextFormatter

The text formatter is used to retrieve formatted text lines from the text store, which is an implementation of TextSource. This is done by first creating an instance of the text formatter by using the Create method. This method creates an instance of the text formatter and sets the maximum line height and width values. As soon as an instance of the text formatter is created, the line creation process is started by calling the FormatLine method. TextFormatter calls back to the text source to retrieve the text and formatting parameters for the runs of text that form a line.

In the following example, the process of formatting a text store is shown. The TextFormatter object is used to retrieve text lines from the text store and then format the text line for drawing into the DrawingContext.

// Create a DrawingGroup object for storing formatted text.
textDest = new DrawingGroup();
DrawingContext dc = textDest.Open();

// Update the text store.
_textStore.Text = textToFormat.Text;
_textStore.FontRendering = _currentRendering;

// Create a TextFormatter object.
TextFormatter formatter = TextFormatter.Create();

// Format each line of text from the text store and draw it.
while (textStorePosition < _textStore.Text.Length)
{
   // Create a textline from the text store using the TextFormatter object.
   using (TextLine myTextLine = formatter.FormatLine(
       _textStore,
       textStorePosition,
       96*6,
       new GenericTextParagraphProperties(_currentRendering),
       null))
   {
       // Draw the formatted text into the drawing context.
       myTextLine.Draw(dc, linePosition, InvertAxes.None);

       // Update the index position in the text store.
       textStorePosition += myTextLine.Length;

       // Update the line position coordinate for the displayed line.
       linePosition.Y += myTextLine.Height;
   }
}

// Persist the drawn text content.
dc.Close();

// Display the formatted text in the DrawingGroup object.
myDrawingBrush.Drawing = textDest;
' Create a DrawingGroup object for storing formatted text.
textDest = New DrawingGroup()
Dim dc As DrawingContext = textDest.Open()

' Update the text store.
_textStore.Text = textToFormat.Text
_textStore.FontRendering = _currentRendering

' Create a TextFormatter object.
Dim formatter As TextFormatter = TextFormatter.Create()

' Format each line of text from the text store and draw it.
Do While textStorePosition < _textStore.Text.Length
   ' Create a textline from the text store using the TextFormatter object.
   Using myTextLine As TextLine = formatter.FormatLine(_textStore, textStorePosition, 96*6, New GenericTextParagraphProperties(_currentRendering), Nothing)
       ' Draw the formatted text into the drawing context.
       myTextLine.Draw(dc, linePosition, InvertAxes.None)

       ' Update the index position in the text store.
       textStorePosition += myTextLine.Length

       ' Update the line position coordinate for the displayed line.
       linePosition.Y += myTextLine.Height
   End Using
Loop

' Persist the drawn text content.
dc.Close()

' Display the formatted text in the DrawingGroup object.
myDrawingBrush.Drawing = textDest

Implementing the Client Text Store

When you extend the text formatting engine, you are required to implement and manage all aspects of the text store. This is not a trivial task. The text store is responsible for tracking text run properties, paragraph properties, embedded objects, and other similar content. It also provides the text formatter with individual TextRun objects which the text formatter uses to create TextLine objects.

To handle the virtualization of the text store, the text store must be derived from TextSource. TextSource defines the method the text formatter uses to retrieve text runs from the text store. GetTextRun is the method used by the text formatter to retrieve text runs used in line formatting. The call to GetTextRun is repeatedly made by the text formatter until one of the following conditions occurs:

  • A TextEndOfLine or a subclass is returned.

  • The accumulated width of text runs exceeds the maximum line width specified in either the call to create the text formatter or the call to the text formatter's FormatLine method.

  • A Unicode newline sequence, such as "CF", "LF", or "CRLF", is returned.

Providing Text Runs

The core of the text formatting process is the interaction between the text formatter and the text store. Your implementation of TextSource provides the text formatter with the TextRun objects and the properties with which to format the text runs. This interaction is handled by the GetTextRun method, which is called by the text formatter.

The following table shows some of the predefined TextRun objects.

TextRun Type Usage
TextCharacters The specialized text run used to pass a representation of character glyphs back to the text formatter.
TextEmbeddedObject The specialized text run used to provide content in which measuring, hit testing, and drawing is done in whole, such as a button or image within the text.
TextEndOfLine The specialized text run used to mark the end of a line.
TextEndOfParagraph The specialized text run used to mark the end of a paragraph.
TextEndOfSegment The specialized text run used to mark the end of a segment, such as to end the scope affected by a previous TextModifier run.
TextHidden The specialized text run used to mark a range of hidden characters.
TextModifier The specialized text run used to modify properties of text runs in its scope. The scope extends to the next matching TextEndOfSegment text run, or the next TextEndOfParagraph.

Any of the predefined TextRun objects can be subclassed. This allows your text source to provide the text formatter with text runs that include custom data.

The following example demonstrates a GetTextRun method. This text store returns TextRun objects to the text formatter for processing.

// Used by the TextFormatter object to retrieve a run of text from the text source.
public override TextRun GetTextRun(int textSourceCharacterIndex)
{
   // Make sure text source index is in bounds.
   if (textSourceCharacterIndex < 0)
      throw new ArgumentOutOfRangeException("textSourceCharacterIndex", "Value must be greater than 0.");
   if (textSourceCharacterIndex >= _text.Length)
   {
      return new TextEndOfParagraph(1);
   }

   // Create TextCharacters using the current font rendering properties.
   if (textSourceCharacterIndex < _text.Length)
   {
      return new TextCharacters(
         _text,
         textSourceCharacterIndex,
         _text.Length - textSourceCharacterIndex,
         new GenericTextRunProperties(_currentRendering));
   }

   // Return an end-of-paragraph if no more text source.
   return new TextEndOfParagraph(1);
}
' Used by the TextFormatter object to retrieve a run of text from the text source.
Public Overrides Function GetTextRun(ByVal textSourceCharacterIndex As Integer) As TextRun
   ' Make sure text source index is in bounds.
   If textSourceCharacterIndex < 0 Then
      Throw New ArgumentOutOfRangeException("textSourceCharacterIndex", "Value must be greater than 0.")
   End If
   If textSourceCharacterIndex >= _text.Length Then
      Return New TextEndOfParagraph(1)
   End If

   ' Create TextCharacters using the current font rendering properties.
   If textSourceCharacterIndex < _text.Length Then
      Return New TextCharacters(_text, textSourceCharacterIndex, _text.Length - textSourceCharacterIndex, New GenericTextRunProperties(_currentRendering))
   End If

   ' Return an end-of-paragraph if no more text source.
   Return New TextEndOfParagraph(1)
End Function

Note

In this example, the text store provides the same text properties to all of the text. Advanced text stores would need to implement their own span management to allow individual characters to have different properties.

Specifying Formatting Properties

TextRun objects are formatted by using properties provided by the text store. These properties come in two types, TextParagraphProperties and TextRunProperties. TextParagraphProperties handle paragraph inclusive properties such as TextAlignment and FlowDirection. TextRunProperties are properties that can be different for each text run within a paragraph, such as foreground brush, Typeface, and font size. To implement custom paragraph and custom text run property types, your application must create classes that derive from TextParagraphProperties and TextRunProperties respectively.

See also