December 2013

Volume 28 Number 12

DirectX Factor - Character Outline Geometries Gone Wild

By Charles Petzold

Charles PetzoldThe most significant advance in digital typography on personal computers occurred more than 20 years ago with the switch from bitmap fonts to outline fonts. In versions of Windows prior to Windows 3.1, onscreen text was generated from font files consisting of tiny bitmaps of specific point sizes. These bitmaps could be scaled for in-between sizes, but not without a loss of fidelity.

Adobe Systems Inc. pioneered an alternative approach to displaying computer fonts with PostScript, which defined font characters with graphic outlines consisting of straight lines and Bézier curves. You could scale these outlines to any dimension, and algorithmic “hints” helped preserve fidelity at small point sizes. As an alternative to PostScript for fonts on the personal computer screen, Apple Inc. developed the TrueType font specification, which Microsoft later adopted. That eventually evolved into today’s common OpenType standard.

These days, we take for granted the continuous scalability of onscreen fonts, as well as the ability to rotate or skew text using graphical transforms. Yet it’s also possible to obtain the actual geometries that define the outlines of these font characters and use them for unusual purposes, such as outlining text characters, or clipping, or performing non-linear transforms.

From Font to Clipping Geometry

If you want to obtain character outline geometries in a Windows Store application, the Windows Runtime API won’t help. You’ll have to use DirectX. The GetGlyphRunOutline method of IDWriteFontFace writes the character outlines into an IDWriteGeometrySink (which is the same as an ID2D1SimplifiedGeometrySink) that defines (or contributes to) an ID2D1PathGeometry object.

Figure 1 shows the constructor of a rendering class in a Windows 8.1 application named ClipToText that I created from the DirectX App (XAML) template. The project includes the distributable Miramonte Bold font file, and the code shows how to convert a glyph run to a path geometry. As usual, I’ve removed the checks of errant HRESULT values for purposes of clarity.

Figure 1 Converting a Glyph Run to a Path Geometry

ClipToTextRenderer::ClipToTextRenderer(
  const std::shared_ptr<DeviceResources>& deviceResources) :
  m_deviceResources(deviceResources)
{
  // Get font file
  ComPtr<IDWriteFactory> factory = m_deviceResources->GetDWriteFactory();
  String^ filePath = Package::Current->InstalledLocation->Path +
     "\\Fonts\\Miramob.ttf";
  ComPtr<IDWriteFontFile> fontFile;
  factory->CreateFontFileReference(filePath->Data(), 
    nullptr, &fontFile);
  // Get font face
  ComPtr<IDWriteFontFace> fontFace;
  factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE,
                          1,
                          fontFile.GetAddressOf(),
                          0,
                          DWRITE_FONT_SIMULATIONS_NONE,
                          &fontFace);
  // Create path geometry and open it
  m_deviceResources->GetD2DFactory()->CreatePathGeometry(&m_clipGeometry);
  ComPtr<ID2D1GeometrySink> geometrySink;
  m_clipGeometry->Open(&geometrySink);
  // Get glyph run outline ("CLIP")
  uint16 glyphIndices [] = { 0x0026, 0x002F, 0x002C, 0x0033 };
  float emSize = 96.0f;
      // 72 points, arbitrary in this program
  fontFace->GetGlyphRunOutline(emSize,
                               glyphIndices,
                               nullptr,
                               nullptr,
                               ARRAYSIZE(glyphIndices),
                               false,
                               false,
                               geometrySink.Get());
  // Don't forget to close the geometry sink!
  geometrySink->Close();
  CreateDeviceDependentResources();
}

Although the code in Figure 1 obtains an IDWriteFontFace object from a privately loaded font, applications can also obtain font face objects from fonts in font collections, including the system font collection. The code in Figure 1 specifies glyph indices explicitly corresponding to the text “CLIP,” but you can also derive glyph indices from a Unicode character string using the GetGlyphIndices method.

Once you’ve created an ID2D1PathGeometry object, you can use it for filling (in which case the result looks just like rendered text), drawing (which renders just the outlines), or clipping. Figure 2 shows a Render method that scales and translates the path geometry to define a clipping region that encompasses the entire display area. Keep in mind the path geometry has both negative and positive coordinates. The (0, 0) origin of the path geometry corresponds to the baseline at the start of the glyph run.

Figure 2 Clipping with a Path Geometry

bool ClipToTextRenderer::Render()
{
  if (!m_needsRedraw)
    return false;
  ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
  Windows::Foundation::Size outputBounds = m_deviceResources->GetOutputBounds();
  context->SaveDrawingState(m_stateBlock.Get());
  context->BeginDraw();
  context->Clear(ColorF(ColorF::DarkBlue));
  // Get the clip geometry bounds
  D2D_RECT_F geometryBounds;
  m_clipGeometry->GetBounds(D2D1::IdentityMatrix(), &geometryBounds);
  // Define transforms to center and scale clipping geometry
  Matrix3x2F orientationTransform =
     m_deviceResources->GetOrientationTransform2D();
  Matrix3x2F translateTransform =
     Matrix3x2F::Translation(SizeF(-geometryBounds.left, -geometryBounds.top));
  float scaleHorz = outputBounds.Width / 
                    (geometryBounds.right - geometryBounds.left);
  float scaleVert = outputBounds.Height / 
                    (geometryBounds.bottom - geometryBounds.top);
  Matrix3x2F scaleTransform = Matrix3x2F::Scale(SizeF(scaleHorz, scaleVert));
  // Set the geometry for clipping
  ComPtr<ID2D1Layer> layer;
  context->CreateLayer(&layer);
  context->PushLayer(
    LayerParameters(InfiniteRect(),
                    m_clipGeometry.Get(),
                    D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
                    translateTransform * scaleTransform
                         * orientationTransform), layer.Get());
  // Draw lines radiating from center
  translateTransform = Matrix3x2F::Translation(outputBounds.Width / 2,
                         outputBounds.Height / 2);
  for (float angle = 0; angle < 360; angle += 1)
  {
    Matrix3x2F rotationTransform = Matrix3x2F::Rotation(angle);
    context->SetTransform(rotationTransform * translateTransform * 
                          orientationTransform);
    context->DrawLine(Point2F(0, 0),
                      Point2F(outputBounds.Width / 2, outputBounds.Height / 2),
                      m_whiteBrush.Get(), 2);
  }
  context->PopLayer();
  HRESULT hr = context->EndDraw();
  if (hr != D2DERR_RECREATE_TARGET)
  {
    DX::ThrowIfFailed(hr);
  }
  context->RestoreDrawingState(m_stateBlock.Get());
  m_needsRedraw = false;
  return true;
}

The Render method then draws a series of lines that radiate from the center of the screen, creating the image shown in Figure 3.

The ClipToText Display
Figure 3 The ClipToText Display

Deeper into Geometry Definitions

Generally speaking, a path geometry is a collection of figures, each of which is a collection of connected segments. These segments take the form of straight lines, quadratic and cubic Bézier curves, and arcs (which are curves on the circumference of an ellipse). A figure can be either closed, in which case the endpoint is connected to the start point, or open.

The GetGlyphRunOutline method writes the glyph outlines into an IDWriteGeometrySink, which is the same as an ID2D1SimplifiedGeometrySink. This in turn is the parent class to a regular ID2D1GeometrySink. Using ID2D1SimplifiedGeometry­Sink instead of ID2D1GeometrySink implies that the resultant path geometry contains figures consisting solely of straight lines and cubic Bézier curves—no quadratic Bézier curves and no arcs.

For font character outlines, these segments are always closed—that is, the endpoint of the figure connects to the start point. The path geometry created in the ClipToText program for the characters “CLIP” consists of five figures—one figure for each of the first three letters and two for the last letter to account for the inside of the upper part of the P.

Perhaps you’d like access to the actual lines and Bézier curves that make up the path geometry so you can manipulate them in weird and unusual ways. At first, this doesn’t seem possible. Once an ID2D1PathGeometry object has been initialized with data, the object is immutable, and the interface provides no way to obtain the contents.

There is a solution, though: You can write your own class that implements the ID2D1SimplifiedGeometrySink interface, and pass an instance of that class to the GetGlyphRunOutline method. Your custom implementation of ID2D1SimplifiedGeometrySink must contain methods named BeginFigure, AddLines, AddBeziers and EndFigure (among a few others). In these methods you can save the entire path geometry in a tree of structures you can define.

This is what I did. The structures I defined for saving the contents of a path geometry are shown in Figure 4. These structures show how a path geometry is a collection of path figure objects, and each path figure is a collection of connected segments consisting of straight lines and cubic Bézier curves.

Figure 4 Structures for Saving the Contents of a Path Geometry

struct PathSegmentData
{
  bool IsBezier;
  std::vector<D2D1_POINT_2F> Points;
          // for IsBezier == false
  std::vector<D2D1_BEZIER_SEGMENT> Beziers;
   // for IsBezier == true
};
struct PathFigureData
{
  D2D1_POINT_2F StartPoint;
  D2D1_FIGURE_BEGIN FigureBegin;
  D2D1_FIGURE_END FigureEnd;
  std::vector<PathSegmentData> Segments;
};
struct PathGeometryData
{
  D2D1_FILL_MODE FillMode;
  std::vector<PathFigureData> Figures;
  Microsoft::WRL::ComPtr<ID2D1PathGeometry>
  GeneratePathGeometry(ID2D1Factory * factory);
};

My implementation of the ID2D1SimplifiedGeometrySink is called InterrogableGeometrySink, so named because it contains a method that returns the resultant geometry as a PathGeometryData object. The most interesting parts of InterrogableGeometrySink are shown in Figure 5.

Figure 5 Most of the InterrogableGeometrySink Class

void InterrogableGeometrySink::BeginFigure(D2D1_POINT_2F startPoint,
                    D2D1_FIGURE_BEGIN figureBegin)
{
  m_pathFigureData.StartPoint = startPoint;
  m_pathFigureData.FigureBegin = figureBegin;
  m_pathFigureData.Segments.clear();
}
void InterrogableGeometrySink::AddLines(const D2D1_POINT_2F *points,
                UINT pointsCount)
{
  PathSegmentData polyLineSegment;
  polyLineSegment.IsBezier = false;
  polyLineSegment.Points.assign(points, points + pointsCount);
  m_pathFigureData.Segments.push_back(polyLineSegment);
}
void InterrogableGeometrySink::AddBeziers(const D2D1_BEZIER_SEGMENT *beziers,
                   UINT beziersCount)
{
  PathSegmentData polyBezierSegment;
  polyBezierSegment.IsBezier = true;
  polyBezierSegment.Beziers.assign(beziers, beziers + beziersCount);
  m_pathFigureData.Segments.push_back(polyBezierSegment);
}
void InterrogableGeometrySink::EndFigure(D2D1_FIGURE_END figureEnd)
{
  m_pathFigureData.FigureEnd = figureEnd;
  m_pathGeometryData.Figures.push_back(m_pathFigureData);
}
HRESULT InterrogableGeometrySink::Close()
{
  // Assume that the class accessing the geometry sink knows what it's doing
  return S_OK;
}
// Method for this implementation
PathGeometryData InterrogableGeometrySink::GetPathGeometryData()
{
  return m_pathGeometryData;
}

Simply pass an instance of InterrogableGeometrySink to GetGlyphRunOutline to get the PathGeometryData object that describes the character outlines. PathGeometryData also contains a method named GeneratePathGeometry that uses the tree of figures and segments to create an ID2D1PathGeometry object you can then use for drawing, filling or clipping. The difference is that prior to calling GeneratePathGeometry, your program can modify the points that make up the line and Bézier segments. You can even add or remove segments or figures.

The InterrogableGeometrySink class and the supporting structures are part of a project named RealTextEditor; by “Real” I mean you can edit the text outlines instead of the text itself. When the program comes up, it displays the large characters “DX.” Tap or click the screen to toggle edit mode. In edit mode, the characters are outlined and dots appear.

Green dots mark the beginnings and ends of line segments and Bézier segments. Red dots are Bézier control points. Control points are connected to corresponding endpoints with red lines. You can grab those dots with the mouse—they’re a little too small for fingers—and drag them, distorting the text characters in weird ways, as Figure 6 demonstrates.

Modified Character Outlines in RealTextEditor
Figure 6 Modified Character Outlines in RealTextEditor

RealTextEditor has no facility to save your custom character geometries, but you could certainly add one. The intent of this program isn’t really to edit font characters, but to clearly illustrate how font characters are defined by a series of straight lines and Bézier curves connected into closed figures—in this case three figures, two for the inside and outside of the D and another for the X.

Algorithmic Manipulations

Once you have a path geometry definition in the form of structures such as PathGeometryData, PathFigureData, and PathSegmentData, you can also manipulate the individual points algorithmically, twisting and turning characters in whatever way you please, perhaps creating an image such as that shown in Figure 7.

The OscillatingText Program
Figure 7 The OscillatingText Program

Well, not quite. The image shown in Figure 7 is not possible using a PathGeometryData object generated from the Interrogable­GeometrySink class I’ve just shown you. In many simple sans-serif fonts, the capital H consists of 12 points connected by straight lines. If you’re dealing solely with those points, there’s no way you can modify them so the straight lines of the H become curves.

However, you can solve that problem with an enhanced version of InterrogableGeometrySink called InterpolatableGeometrySink. Whenever this new class encounters a straight line in the AddLines method, it breaks that line into multiple smaller lines. (You can control this feature with a constructor argument.) The result is a completely malleable path geometry definition.

The OscillatingText program responsible for the image in Figure 7 actually swings the interior of the characters back and forth, much like a hula dance. This algorithm is implemented in the Update method in the rendering class. Two copies of a PathGeometryData are retained: The source (identified as “src”) describes the original text outline, and the destination (“dst”) contains modified points based on the algorithm. The Update method concludes by calling GeneratePathGeometry on the destination structure, and that’s what the program displays in its Render method.

Sometimes when algorithmically altering a path geometry, you might prefer working solely with lines rather than Bézier curves. You can do that. You can define an ID2D1PathGeometry object from a call to GetGlyphRunOutline, and then call Simplify on that ID2D1PathGeometry using the D2D1_GEOMETRY_SIMPLI­FICATION_OPTION_LINES constant and an Interpolatable­GeometrySink instance.

From DirectX to the Windows Runtime

If you’re acquainted with the Windows Runtime API, the PathGeometryData, PathFigureData and PathSegmentData structures in Figure 4 probably seem very familiar. The Windows::Xaml::UI::Media namespace contains similar classes named PathGeometry, PathFigure and PathSegment, from which PolyLineSegment and PolyBezierSegment derive. These are the classes you use to define a path geometry in the Windows Runtime, which you normally render using the Path element.

Of course, the similarity shouldn’t be surprising. After all, the Windows Runtime is built on DirectX. What this similarity implies is that you can write a class that implements ID2D1Simpli­fiedGeometrySink to build a tree of PathGeometry, PathFigure, PolyLineSegment and PolyBezierSegment objects. The resultant PathGeometry object is directly usable by a Windows Runtime application and can be referenced in a XAML file. (You could also write an ID2D1SimplifiedGeometrySink implementation that generates a XAML representation of a PathGeometry and insert that into a XAML file in any XAML-based environment, such as Silverlight.)

The TripleAnimatedOutline solution demonstrates this technique. The solution contains a Windows Runtime Component project named SimpleDWriteLib that contains a public ref class named TextGeometryGenerator, which provides access to the system fonts and generates outline geometries based on these fonts. Because this ref class is part of a Windows Runtime Component, the public interface consists solely of Windows Runtime types. I made that public interface consist mostly of dependency properties so it could be used with bindings in a XAML file. The SimpleDWriteLib project also contains a private class named InteroperableGeometrySink that implements the ID2D1SimplifiedGeometrySink interface and constructs a Windows Runtime PathGeometry object.

You can then use this PathGeometry with a Path element. But watch out: When the Windows Runtime layout engine computes the size of a Path element for layout purposes, it only uses positive coordinates. To make the PathGeometry easier to use in a XAML file, TextGeometryGenerator defines a DWRITE_GLYPH_OFFSET that modifies coordinates based on the capHeight field of the font metrics structure. This serves to adjust the geometry coordinates to begin at the top of the font characters rather than at the origin, and to eliminate most negative coordinates.

To demonstrate the interoperability of the SimpleDWriteLib component, the TripleAnimatedOutline application project is written in Visual Basic. But don’t worry: I didn’t have to write any Visual Basic code. Everything I added to this project is in the MainPage.xaml file shown in Figure 8. The ListBox displays all the fonts on the user’s system, and an outline geometry based on the selected font is animated in three ways:

  • Dots travel around the characters;
  • A gradient brush sweeps past the text;
  • A projection transform spins it around the vertical axis.

Figure 8 The TripleAnimatedOutline XAML File

<Page
  x:Class="TripleAnimatedOutline.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:TripleAnimatedOutline"
  xmlns:dwritelib="using:SimpleDWriteLib">
  <Page.Resources>
    <dwritelib:TextGeometryGenerator x:Key="geometryGenerator"
                                     Text="Outline"
                                     FontFamily="Times New Roman"
                                     FontSize="192"
                                     FontStyle="Italic" />
  </Page.Resources>
  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <ListBox Grid.Column="0"
             ItemsSource="{Binding Source={StaticResource geometryGenerator},
                                   Path=FontFamilies}"
             SelectedItem="{Binding Source={StaticResource geometryGenerator},
                                    Path=FontFamily,
                                    Mode=TwoWay}" />
    <Path Name="path"
          Grid.Column="1"
          Data="{Binding Source={StaticResource geometryGenerator}, Path=Geometry}"
          Fill="LightGray"
          StrokeThickness="6"
          StrokeDashArray="0 2"
          StrokeDashCap="Round"
          HorizontalAlignment="Center"
          VerticalAlignment="Center">
      <Path.Stroke>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0"
                                     SpreadMethod="Reflect">
          <GradientStop Offset="0" Color="Red" />
          <GradientStop Offset="1" Color="Blue" />
          <LinearGradientBrush.RelativeTransform>
            <TranslateTransform x:Name="brushTransform" />
          </LinearGradientBrush.RelativeTransform>
        </LinearGradientBrush>
      </Path.Stroke>
      <Path.Projection>
        <PlaneProjection x:Name="projectionTransform" />
      </Path.Projection>
    </Path>
  </Grid>
  <Page.Triggers>
    <EventTrigger>
      <BeginStoryboard>
        <Storyboard>
          <DoubleAnimation Storyboard.TargetName="path"
                           Storyboard.TargetProperty="StrokeDashOffset"
                           EnableDependentAnimation="True"
                           From="0" To="2" Duration="0:0:1"
                           RepeatBehavior="Forever" />
          <DoubleAnimation Storyboard.TargetName="brushTransform"
                           Storyboard.TargetProperty="X"
                           EnableDependentAnimation="True"
                           From="0" To="2" Duration="0:0:3.1"
                           RepeatBehavior="Forever" />
          <DoubleAnimation Storyboard.TargetName="projectionTransform"
                           Storyboard.TargetProperty="RotationY"
                           EnableDependentAnimation="True"
                           From="0" To="360" Duration="0:0:6.7"
                           RepeatBehavior="Forever" />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Page.Triggers>
</Page>

A second program also uses SimpleDWriteLib. This is RippleText, a C# program that uses a CompositionTarget.Rendering event to perform an animation in code. Similar to OscillatingText, RippleText obtains two identical PathGeometry objects. It uses one as an immutable source and the other as a destination whose points are algorithmically transformed. The algorithm involves an animated sine curve that’s applied to the vertical coordinates, resulting in distortions such as those shown in Figure 9.

The RippleText Display
Figure 9 The RippleText Display

Although the examples I’ve shown here are extreme in many ways, you certainly have the option to create subtler effects. I suspect that much of the WordArt feature in Microsoft Word is built around techniques involving the manipulation of character outlines, so that might provide some inspiration.

You can also integrate these techniques into more normal text-display code based on IDWriteTextLayout. This interface has a method named Draw that accepts an instance of a class that implements the IDWriteTextRenderer interface. That’s a class you’ll write yourself to get access to the DWRITE_GLYPH_RUN object that describes the text to be rendered. You can make changes to the glyph run and then render the modified version, or you can generate the character outline geometries at that point and modify the outlines prior to rendering.

Much of the power of DirectX lies in its flexibility and adaptability to different scenarios.


Charles Petzold is a longtime contributor to MSDN Magazine and the author of “Programming Windows, 6th edition” (Microsoft Press, 2012), a book about writing applications for Windows 8. His Web site is charlespetzold.com.

Thanks to the following technical expert for reviewing this article: Jim Galasyn (Microsoft)