Easily render rotated text in a WPF program using C#

The example Use an extension method to easily render text in a WPF program using C# shows how to make an extension method that makes rendering text easy in WPF. This example makes a similar extension method for rendering rotated text.

The title of this post includes the word "easily." By that I mean it's easy to use the extension method, not that it's easy to write the method.


This is a fairly confusing extension method, so I'm going to show the code in pieces.

Most of the extension method's parameters are straightforward, but I want to explain the last three before you read the code. The following code fragment shows the method's declaration with the last three parameters highlighted in blue.

// Draw rotated text at the indicated location.
public static void DrawRotatedString(this DrawingContext drawing_context,
    string text, double angle, string font_name, double em_size, Brush brush,
    Point origin, TextAlignment text_align,
    VertAlignment valign, TextAlignment halign)
{
   ...
}

The text_align parameter determines how the text is aligned. It can take the values Left, Center, and Right. (The TextAlignment enumeration also provides the Justify value, but WPF seems to treat it like Left, so this example does, too.) The picture on the right shows the results produced by the different alignments. If you look at the picture at the top of the post, you'll see that Center alignment is selected so those values are centered inside their red bounding boxes. (This is most obvious with the TOP CENTER entry because "TOP" and "CENTER" have such different lengths.)

The extension method's last two parameters tell how the result should be aligned with respect to the text's point of origin. The dashed blue rectangles in the picture at the top of this post show the bounding boxes needed to hold the rotated text. The method then moves the text so gthe blue bounding boxes are appropriately aligned with respect to the text origin. For example, the TOP LEFT text is aligned so its bounding box is aligned to the text origin on its top and left.

The following code shows how the method gets ready to draw text.

    Typeface typeface = new Typeface(font_name);
    FormattedText formatted_text = new FormattedText(
        text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight,
        typeface, em_size, brush);
    formatted_text.TextAlignment = text_align;

This code creates the necessary Typeface and FormattedText objects. It sets the FormattedText object's TextAlignment property to align the text properly.

The basic idea of the code that follows is to draw the text at the origin point (0, 0) and then apply transformations to make the text appear rotated and in the desired location. Transformations allow you to make the drawing system move, rotate, and stretch drawing components as you draw them. If you want more details, see the post Use transformations to map points from one coordinate system to another when drawing in C#.

This method uses transformations to: (1) translate the result to center the text at the origin (0, 0), (2) rotate the text, and finally (3) translate the text to its final destination.

The next piece of code creates the first transformation, which moves the text so it is centered at (0, 0).

    // Make a transformation to center the text.
    double width = formatted_text.Width - formatted_text.OverhangLeading;
    double height = formatted_text.Height;
    TranslateTransform translate1 = new TranslateTransform();
    translate1.Y = -height / 2;
    if ((text_align == TextAlignment.Left) ||
        (text_align == TextAlignment.Justify))
            translate1.X = -width / 2;
    else if (text_align == TextAlignment.Right) translate1.X = width / 2;
    else translate1.X = 0;

This code first calculates the text's width and height. The height is simply given by the FormattedText's Height property. The width is given by formatted_text.Width - formatted_text.OverhangLeading. (I'll explain this further in my next post.)

The code then creates a TranslateTransform object and sets its Y property to -height / 2. That translates in the negative Y direction by half of the text's height to center the text vertically.

Next the code sets the transformation's X property to center the text horizontally. If the text is aligned on the left (or justified), then when it is drawn at (0, 0), the text appears to the right of that point. In this case, the code sets the transformation's X property to -width / 2 to move the text left and center it horizontally.

If the text is aligned on the right, then when it is drawn at (0, 0), the text appears to the left of that point. In that case, the code sets the transformation's X property to width / 2 to move the text right and center it horizontally.

Finally if the text is centered, the code sets the transformation's X property to 0 so it remains centered at (0, 0).

The next piece of code creates the second transformation, which rotates the text.

    // Make a transformation to rotate the text.
    RotateTransform rotate = new RotateTransform(angle);

This one is easy. The code just makes a RotateTransform object, passing its constructor the angle by which the code should be rotated.

The next piece of code creates the third transformation, which moves the text to its final location.

    // Get the text's bounding rectangle.
    Rect rect = new Rect(0, 0, width, height);
    if (text_align == TextAlignment.Center) rect.X -= width / 2;
    else if (text_align == TextAlignment.Right) rect.X -= width;

    // Get the rotated bounding rectangle.
    Rect rotated_rect = rotate.TransformBounds(rect);

    // Make a transformation to center the
    // bounding rectangle at the destination.
    TranslateTransform translate2 = new TranslateTransform(origin.X, origin.Y);

    // Adjust the translation for the desired alignment.
    if (halign == TextAlignment.Left)
        translate2.X += rotated_rect.Width / 2;
    else if (halign == TextAlignment.Right)
        translate2.X -= rotated_rect.Width / 2;
    if (valign == VertAlignment.Top)
        translate2.Y += rotated_rect.Height / 2;
    else if (valign == VertAlignment.Bottom)
        translate2.Y -= rotated_rect.Height / 2;

To correctly position the text, the code needs to know how big the text is after it has been rotated. (See the dashed blue rectangles in the picture at the top of the post.) To figure that out, the code creates a Rect that represents the text's original (untransformed) drawing area. Depending on the text's alignment, the code adjusts the Rect's X property to center the rectangle horizontally. This makes the rectangle represent the area where the text will be drawn.

Next the code uses the rotation transformation's RotateBounds method to find a new rectangle that represents bounds the rotated original rectangle rect. (It has the dimensions of the dashed blue rectangles in the picture at the top of the post.)

The code then creates a TranslateTransform that moves a point at the origin to the desired destination point (origin.X, origin.Y). If you left things as they are at this point, the text and its red bounding box would be rotated and translated so it was centered at (origin.X, origin.Y). The code adjusts the final TranslateTransform to align the text properly with respect to that point.

The rest of the method is a lot easier to understand. The following code shows how the method applies the three transformations.

    // Push transformations in reverse order. (Thanks Microsoft!)
    drawing_context.PushTransform(translate2);
    drawing_context.PushTransform(rotate);
    drawing_context.PushTransform(translate1);

This code uses the DrawingContext's PushTransform method to add the transformations to its drawing pipeline. For some reason, Microsoft requires you add the transformations to the DrawingContext in reverse order, so this example adds the final translation first, the rotation second, and the initial translation last. (Microsoft drawing methods have required you to pass transformations in reverse order for many years. I've never understood why.)

After the transformations are in place, anything you draw will be transformed by them. The following code shows how the method draws the text and its red bounding box.

    // Draw.
    drawing_context.DrawText(formatted_text, new Point(0, 0));

    // Draw a rectangle around the text. (For debugging.)
    drawing_context.DrawRectangle(null, new Pen(Brushes.Red, 1), rect);

    // Remove the transformations.
    drawing_context.Pop();
    drawing_context.Pop();
    drawing_context.Pop();

First the code calls DrawText to draw the text. It then calls DrawRectangle to draw the red bounding box. Both of those operations are translated, rotated, and translated again as desired.

Next the code calls the DrawingContext's Pop method to remove the three transformations. If you don't do that, the next time you call the extension method, the new transformations are added to the previous ones and you get some really weird and hard-to-debug results.

That's it for the important drawing code. The following code shows the last piece of the method, which draws the dashed blue bounding rectangle.

    // Draw the rotated bounding rectangle. (For debugging.)
    Rect transformed_rect =
        translate2.TransformBounds(
            rotate.TransformBounds(
                translate1.TransformBounds(rect)));
    Pen custom_pen = new Pen(Brushes.Blue, 1);
    custom_pen.DashStyle = new DashStyle(
        new double[] { 5, 5 }, 0);
    drawing_context.DrawRectangle(null, custom_pen, transformed_rect);

This method simply applies the TransfromBounds methods of the three transformations to the text's original bounding rectangle rect. The first translation returns the bounding rectangle centered at the origin. The rotation's TransformBounds method returns a rectangle big enough to hold the rotated centered rectangle. The final translation just moves the resized rectangle so it has the appropriate alignment with the text's drawing origin. The result is one of the blue dashed boxes shown in the picture at the top of this post.

Download the example program to see the complete method all in one piece.

This method's code is fairly confusing, but the method itself is quite easy to use. The following code shows how the example's main program draws the TOP LEFT sample.

point = new Point(x, y);
drawingContext.DrawEllipse(brush, pen, point, 3, 3);
drawingContext.DrawRotatedString("TOP\nLEFT", angle,
    font_name, em_size,
    Brushes.Black, point, text_align,
    VertAlignment.Top, TextAlignment.Left);

The code defines the text's point of origin. It then calls the DrawingContext's DrawEllipse method to mark that point on the window.

Next the code calls the DrawRotatedString extention method. Values such as brush, pen, font_name, and the others are set earlier in the program. they're straightforward so they aren't shown here.

   

 

What did you think of this article?




Trackbacks
  • 4/30/2014 3:18 PM BLOG.CSHARPHELPER.COM wrote:
    In earlier examples such as Easily render rotated text in a WPF program using C#, I explained how to render text in a WPF program. Those examples draw text on the program's window at runtime. Sometimes you may want to render text into a bitmap. This example uses the following RenderTextOntoBitmap method do that. // Render text onto a bitmap. public static RenderTargetBitmap RenderTextOntoBitmap( string text, double dpiX, double dpiY, string font_name, double em_size, TextAlignment text_align, Brush bg_brush, Pen rect_pen, Brush text_brush, ...
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.