Drawing text with Direct2D and DirectWrite with SharpDX

Back when Windows 7 was launched, DirectX 11 introduced a new API for text rendering called DirectWrite. It was intended as a fresh start from the old GDI implementation, featuring full unicode support, ClearType text rendering, OpenType typographic features and much more. One of the advantages of DirectWrite is that although it is a DirectX API, it has been architectured as a standalone component with no dependency on any of the existing rendering ones, meaning that you can use it from Direct3D, Direct2D or even your own custom renderer. As an example of its independence, since version 4 the web browser Mozilla Firefox uses DirectWrite on supported platforms for text rendering.

This tutorial is based on the previous one about basic Direct2D initialization and drawing so go ahead and clone the source code from GitHub if you haven’t done it already.

Initialization

This is very simple. We just instantiate a new SharpDX.DirectWrite.Factory object, which doesn’t need any parameters unless you want to specify if it will be a shared factory to be used across different threads.

DirectWrite factory initialization
  1. // Create the DirectWrite factory object.
  2. SharpDX.DirectWrite.Factory fontFactory = new SharpDX.DirectWrite.Factory();

 

Now it’s time to create the objects that will contain the neccessary information for drawing text.

  • TextFormat is an object that will hold a font’s family, size and other properties like style and weight. We will be using Segoe UI with a size of 24 DIPs and normal weight and style. Note that the font is specified by its family name; in case it isn’t found no errors will be thrown, instead falling back to a default one.
  • TextLayout is a way of arranging a specified string of text inside a rectangle for further drawing. We will be creating two of them for demonstrating the different methods of rendering moving text.
TextFormat and TextLayout creation
  1. // Create a TextFormat object that will use the Segoe UI font with a size of 24 DIPs.
  2. textFormat = new TextFormat(fontFactory, “Segoe UI”, 24.0f);
  3. // Create two TextLayout objects for rendering the moving text.
  4. textLayout1 = new TextLayout(fontFactory, “This is an example of a moving TextLayout object with snapped pixel boundaries.”, textFormat, 400.0f, 200.0f);
  5. textLayout2 = new TextLayout(fontFactory, “This is an example of a moving TextLayout object with no snapped pixel boundaries.”, textFormat, 400.0f, 200.0f);

Drawing

Drawing text is quite straightforward. Since the Direct2D DeviceContext object already has functions to render text using DirectWrite objects, we won’t need to roll out our own glyph rasterizer. Just a one line call to SharpDX.Direct2D1.DeviceContext.DrawText inside a BeginDraw/EndDraw block will output our text on screen.

Here we are going to draw two lines of text with a predefined layout region. The difference is that the first one will be clipped to that region (DrawTextOptions.Clip) while the second one won’t (DrawTextOptions.None).

Drawing normal and clipped text
  1. // Draw a block of text that will be clipped against the specified layout rectangle.
  2. d2dContext.FillRectangle(new RectangleF(50, 50, 200, 200), backgroundBrush);
  3. d2dContext.DrawText(“This text is long enough to overflow the designed region but will be clipped to the containing rectangle. Lorem ipsum dolor sit amet, consectetur adipiscing elit. “, textFormat, newRectangleF(50, 50, 200, 200), textBrush, DrawTextOptions.Clip);
  4. // Draw a block of text that will overflow the specified layout rectangle.
  5. d2dContext.FillRectangle(new RectangleF(50, 300, 200, 200), backgroundBrush);
  6. d2dContext.DrawText(“However, this other text isn’t going to be clipped: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean gravida dui id accumsan dictum.”, textFormat, new RectangleF(50, 300, 200, 200), textBrush, DrawTextOptions.None);

 

In case you want the text to appear crisper or more blurred, you can specify different measuring modes when calling DrawText:

  • Natural: Measures the text using the ideal glyph metrics, without taking into account the current screen resolution. This is the default value in case it’s not specified.
  • GdiClassic: Adjusts the glyph’s metrics based on the current display resolution.
  • GdiNatural: Again takes into account the display resolution but treats the font as if it was used for ClearType rendering.

The following image is a capture of the sample with a 400x zoom. Each line is drawn using Natural, GdiClassic and GdiNatural. Note that Natural measuring mode leaves less gap between each glyph. Click the image to view at full size.

MeasuringMode comparison

Now, let’s see how we can draw TextLayout objects. We just have to call SharpDX.Direct2D1.DeviceContext.DrawTextLayout, and since the font and string information are already stored in the TextLayout, we only need to specify the position, parameter and text brush. Using DrawTextLayout instead of DrawText is recommended since DrawText always creates a new TextLayout object internally, thus generating extra garbage.

In the following code, we are drawing two similar TextLayout blocks that move vertically. The difference is that the second one uses DrawTextOptions.NoSnap so the glyphs aren’t snapped to an integer position, allowing for subpixel movement and giving an smoother effect.

Drawing moving TextLayout
  1. // Draw moving text.
  2. d2dContext.FillRectangle(new RectangleF(300, 300, 400, 200), backgroundBrush);
  3. d2dContext.DrawTextLayout(new Vector2(300, 350 + layoutYOffset), textLayout1, textBrush);
  4. // Draw moving text without pixel snapping, thus giving a smoother movement.
  5. d2dContext.FillRectangle(new RectangleF(750, 300, 400, 200), backgroundBrush);
  6. d2dContext.DrawTextLayout(new Vector2(750, 350 + layoutYOffset), textLayout2, textBrush, DrawTextOptions.NoSnap);

 

Note that in the following animated image, the left text’s movement (disregarding minor encoding errors) is more jerky.

MovingTextLayout

That’s it, we have learned how to do basic text drawing with SharpDX. You can get the finished source code from the corresponding GitHub repository.

 

Basic Direct2D drawing with SharpDX

If you are an experienced DirectX programmer chances are you used the DirectDraw API for 2D drawing. If not, here’s a quick summary: it was used for quickly copying parts of one image to another using binary operations (“bit blitting”). This was a quick and good enough method for paletted or low BPP images, but if you wanted to do more advanced operations like rotation or alpha blending you had to implement your own software solution. With the advent of 3D graphics cards, hardware 2D support was phased out in favor of 3D acceleration, and this set the demise of DirectDraw.

Many years later, Direct2D was born to provide true 2D graphics to the DirectX API, instead of drawing 3D shapes using an orthographic projection. It features GPU accelerated raster and vector graphics support, and an immediate mode with a syntax similar to XNA’s SpriteBatch.

In this sample we will add Direct2D drawing to the basic Direct3D app created in the previous tutorial, so get the source code from the repository in case you don’t have it. First we must add the DeviceCreationFlags.BgraSupport parameter to the creation of our Direct3D11.Device, since it’s the color format that Direct2D works with. The next step is to create the default Direct2D device from the existing DXGI.Device2 we initialized previously and use it to generate a Direct2D1.DeviceContext instance:

Direct2D context creation
  1. SharpDX.Direct2D1.Device d2dDevice = new SharpDX.Direct2D1.Device(dxgiDevice2);
  2. d2dContext = new SharpDX.Direct2D1.DeviceContext(d2dDevice, SharpDX.Direct2D1.DeviceContextOptions.None);

Next up we must create a Bitmap1 object that will hold a reference to the backbuffer so Direct2D has a surface to draw on; to do this we have to specify its properties using a BitmapProperties1 declaration:

BitmapProperties1 declaration
  1. BitmapProperties1 properties = newBitmapProperties1(newPixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied),
  2.     DisplayProperties.LogicalDpi, DisplayProperties.LogicalDpi, BitmapOptions.Target | BitmapOptions.CannotDraw);

This way we are going to create a bitmap in 32-bit BGRA format (that’s why we previously changed the DeviceCreationFlags!) with premultiplied alpha; BitmapOptions.Target allows the bitmap to be used as a device context target and BitmapOptions.CannotDraw forgives from using it when creating a brush or other drawing resource.

Next up we get the surface of the swap chain that hold the backbuffer and create the Bitmap1 using it as its source:

Bitmap1 creation
  1. Surface backBuffer = swapChain.GetBackBuffer<Surface>(0);
  2. d2dTarget = newBitmap1(d2dContext, backBuffer, properties);

And it’s ready to use it as our drawing target. Simply assign it to the Target property of the Direct2D1.DeviceContext you created previously and start drawing whatever your want to it.

To draw anything, we have to specify a brush that holds the properties of the stroke used to fill the figure. In this tutorial we use the three basic brushes:

  • SolidColorBrush: just a solid, plain color.
  • LinearGradientBrush: a linear gradient that interpolates between the GradientStop specified in a GradientStopCollection. We use a 2-stop gradient.
  • RadialGradientBrush: another gradient brush, but this time in a radial distribution. You can specify its RadiusX and RadiusY properties independently to get a circle or an ellipse.

Below is the code for drawing, where we set the drawing target and tell the context to start drawing. After clearing it with a well-known color, we fill different geometric shapes with the brushes previously created. EndDraw tells Direct2D to flush the buffer and raster as needed.

Geometry drawing
  1. d2dContext.Target = d2dTarget;
  2. d2dContext.BeginDraw();
  3. d2dContext.Clear(Color.CornflowerBlue);
  4. d2dContext.FillRectangle(new Vector2(50, 50, 450, 150), solidBrush);
  5. d2dContext.FillRoundedRectangle(new RoundedRectangle()
  6. {
  7.     Rect = new Vector2(50, 250, 450, 150),
  8.     RadiusX = 10,
  9.     RadiusY = 10
  10. }, linearGradientBrush);
  11. d2dContext.FillEllipse(new Ellipse(new Vector2(250, 525), 100, 100), radialGradientBrush);
  12. d2dContext.EndDraw();

And this is the colorful screen we will get:

Basic Direct2D drawing final result

As always, get the source code from the GitHub repo.