Charles Petzold



Using Text Outlines in Silverlight

October 5, 2009
New York, N.Y.

Check this out: It's a little Silverlight app that displays some text (in this case the word "Silverlight") in a 144-point font, but the text characters are outlined, which is not something you normally see in a Silverlight app. Moreover, the characters are outlined with a dotted line, and not only that, but the dots are animated so they move around the outlines of the letters.


AnimatedDottedOutlinedText.html

Conceptually, this program is rather simple. If you download the source code, you'll see that everything is done in the MainPage.xaml file:

<UserControl x:Class="AnimatedDottedOutlinedText.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid x:Name="LayoutRoot">
        <Path Name="path"
              HorizontalAlignment="Center"
              VerticalAlignment="Center"
              Stroke="Blue"
              StrokeThickness="6"
              StrokeDashCap="Round"
              StrokeDashArray="0 1.5">
            <Path.Data>
                ...
            </Path.Data>
        </Path>
    </Grid>

    <UserControl.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="path"
                                     Storyboard.TargetProperty="StrokeDashOffset"
                                     From="0" To="1.5" Duration="0:0:1"
                                     RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </UserControl.Triggers>
</UserControl>

The text is displayed with a Path element where the Data property is set to a PathGeometry (not shown) defining the outlines of the text characters. The Stroke and StrokeThickness properties define the brush and thickness of the lines themselves. The StrokeDashCap is set to Round so that if the line is drawn with a non-solid style, any dashes or dots in this line style have rounded end caps.

The StrokeDashArray defines this non-solid style with a collection of two or more numbers. Here, the first number is the length of the dashes (zero) and the second is the length of the space between the dashes (1.5). These values are in units of the stroke thickness. The value of zero may seem a bit strange until you realize that the rounded caps of the dash are beyond the length of the dash, so all we're really seeing are the rounded caps, which show up as dots. The value of 1.5 means that 9 pixels (the StrokeThickness of 6 times 1.5) separate the centers of these dots.

The dots are animated by a DoubleAnimation targetting the StrokeDashOffset property of the Path. The StrokeDashOffset is zero by default, and means that the line begins with the first dot or dash. Values higher than zero are also in units of the stroke thickness, and indicate where the pattern of dots and dashes begins relative to the beginning of the line. Animating this value causes the dots to appear to move around the text characters.

That's all fairly easy. The hard part of this project is what appears in MainPage.xaml where I've inserted an ellipsis. This is the PathGeometry that defines the outlines of the text characters.

If you're a WPF programmer, you known how to generate a Geometry or PathGeometry defining the outlines of text characters. You first create a FormattedText object that comprises a Typeface object (specifying the FontFamily, FontWeight, and so forth) along with the font size and the text itself. (The FormattedText object is customarily used with the DrawText method of the DrawingContext class.) FormattedText has a method named BuildGeometry that returns a Geometry object — in my experience this is always a GeometryGroup object — which you can then convert into a PathGeometry object using the static PathGeometry.Create method.

For some purposes, you might prefer a flattened PathGeometry where all the Bezier curves have been approximated by straight lines. In the flattened PathGeometry every PathFigure contains a single segment of type PolylineSegment. You can obtain such an animal by calling the GetFlattenedPathGeometry method, either on the original Geometry or on the PathGeometry you've obtained from that Geometry.

Well, that's great if you're writing a WPF program, but Silverlight doesn't even have a FormattedText class, which means there's no way at run-time to get any kind of Geometry that defines text outlines.

You can't do it at run-time but you can do it at compile-time. You can write a WPF program that creates a Geometry (or PathGeometry) based on a particular text and font, and then converts that Geometry into XAML using XamlWriter.Save. You can then copy-and-paste that XAML into the Silverlight application.

And that's exactly what I did.

This WPF TextOutlineGenerator source code and program lets you specify most of the parameters for creating a FormattedText object and getting Geometry objects out of it. The program mostly consists of a bunch of ComboBox controls to select font characteristics, and places to type in a font size and the actual text out want to convert.

The program also displays a group of three RadioButton objects labeled "GeometryGroup," "PathGeometry," and "Flattened PathGeometry," that lets you specify the type of object you want. A preview of the text itself is displayed and then the resultant XAML is dumped into a TextBox for copying into your Silverlight app.

And then you'll begin discovering some issues with Silverlight and Geometry objects:

Despite the Silverlight documentation, I have never been able to persuade Silverlight to accept a PathGeometry object in XAML where the Figures property is set to a string composed of path markup syntax. Unfortunately, the XAML generated by a WPF application based on Geometry objects that define text outlines almost always contains Figures properties set to these strings. You can copy the actual path-markup syntax and set it to the Data property of a Path object, but you can't use the PathGeometry objects themselves.

To get around this problem, I've added a CheckBox labeled "Separate into Components." If this is clicked, the program attempts to break down the GeometryGroup into multiple PathGeometry objects (one for each letter of the text), or the PathGeometry into multiple PathFigure objects (one for each connected stroke of each letter of the text). These PathFigure objects contain actual PathSegment objects rather than path markup syntax strings.

For generating the XAML for the AnimatedDottedOutlinedText program, I used TextOutlineGenerator to specify a family of "Times New Roman," a weight of bold, a point-size of 144, the text "Silverlight," generating a PathGeometry separated into components. This gave me a collection of PathFigure objects that I simply copy-and-pasted inside PathGeometry tags within the Path.Data tags. The markup generated by the WPF program contained two property settings that Silverlight doesn't support — IsStroked set to true and IsSmoothJoined set to false — but these were easy to remove. It's also possible to remove the xmlns attributes, but I left them in.

And this is how you can get access to text character outlines in your Silverlight applications, either for outlining or other nefarious purposes. The only requirement is that you need to generate these outlines at compile-time rather than run-time.