DWriteCore overview

DWriteCore is the Windows App SDK implementation of DirectWrite (DirectWrite is the DirectX API for high-quality text rendering, resolution-independent outline fonts, and full Unicode text and layout support). DWriteCore is a form of DirectWrite that runs on versions of Windows down to Windows 10, version 1809 (10.0; Build 17763). DWriteCore implements the same API as DirectWrite, with a few additions as described in this topic.

This introductory topic describes what DWriteCore is, and shows how to install it into your dev environment and program with it.

For an app that already uses DirectWrite, switching to DWriteCore requires minimal changes:

In return, the app gets the benefits of Windows App SDK—namely, access to the newest APIs and functionality regardless of what version of Windows your customer is running.

Tip

For descriptions of and links to DirectX components in active development, see the blog post DirectX Landing Page.

The value proposition of DWriteCore

DirectWrite itself supports a rich array of features that makes it the font-rendering tool of choice on Windows for most apps—whether that's through direct calls, or via Direct2D. DirectWrite includes a device-independent text layout system, high quality sub-pixel Microsoft ClearType text rendering, hardware-accelerated text, multi-format text, advanced OpenType® typography features, wide language support, and GDI-compatible layout and rendering. DirectWrite has been available since Windows Vista SP2, and it has evolved over the years to include more advanced features such as variable fonts, which enables you to apply styles, weights, and other attributes to a font with only one font resource.

Due to the long lifespan of DirectWrite, however, advances in development have tended to leave older versions of Windows behind. In addition, DirectWrite's status as the premier text rendering technology is limited only to Windows, leaving cross-platform applications to either write their own text-rendering stack, or to rely on 3rd-party solutions.

DWriteCore solves the fundamental problems of version feature orphaning and cross-platform compatibility by removing the library from the system, and targeting all possible supported endpoints. To that end, we've integrated DWriteCore into Windows App SDK.

The primary value that DWriteCore gives you, as a developer, in Windows App SDK is that it provides access to many (and eventually all) DirectWrite features. All features of DWriteCore will function the same on all down-level versions without any disparity regarding which features might work on which versions.

The DWriteCore demo app—DWriteCoreGallery

DWriteCore is demonstrated by way of the DWriteCoreGallery sample app, which is available for you now to download and study.

Get started with DWriteCore

DWriteCore is part of the Windows App SDK. This section describes how to set up your development environment for programming with DWriteCore.

Install tools for the Windows App SDK

See Install tools for the Windows App SDK.

Create a new project

In Visual Studio, create a new project from the Blank App, Packaged (WinUI 3 in Desktop) project template. You can find that project template by choosing language: C++; platform: Windows App SDK; project type: Desktop.

For more info, see Project templates for WinUI 3.

Install the Microsoft.ProjectReunion.DWrite NuGet package

In Visual Studio, click Project > Manage NuGet Packages... > Browse, type or paste Microsoft.ProjectReunion.DWrite in the search box, select the item in search results, and then click Install to install the package for that project.

Alternatively, begin with the DWriteCoreGallery sample app

Alternatively, you can program with DWriteCore by beginning with the DWriteCoreGallery sample app project, and base your development on that project. You can then feel free to remove any existing source code (or files) from that sample project, and to add any new source code (or files) to the project.

Use DWriteCore in your project

For more info about programming with DWriteCore, see the Programming with DWriteCore section later in this topic.

The release phases of DWriteCore

Porting DirectWrite to DWriteCore is a sufficiently large project to span multiple Windows release cycles. That project is divided into phases, each of which corresponds to a chunk of functionality delivered in a release.

Features in the current release of DWriteCore

DWriteCore is part of the Windows App SDK. It contains the basic tools that you, as a developer, need to consume DWriteCore, including the following features.

A banner feature is color fonts. Color fonts enable you to render your fonts with more sophisticated color functionality beyond simple single colors. For example, color fonts is what powers the ability to render emoji and toolbar icon fonts (the latter of which is used by Office, for example). Color fonts were first introduced in Windows 8.1, but the feature was heavily expanded upon in Windows 10, version 1607 (Anniversary Update).

The work on cleanup of the font cache, and the in-memory font loader, allow for faster loading of fonts, and memory improvements.

With these features, you can immediately begin to harness some of DirectWrite's modern core functionality—such as variable fonts. Variable fonts are one of the most important features for DirectWrite customers.

Our invitation to you as a DirectWrite developer

DWriteCore, along with other Windows App SDK components, will be developed with openness to developer feedback. We invite you to begin exploring DWriteCore, and to provide insights or requests into feature development on our Windows App SDK GitHub repository.

Programming with DWriteCore

Just like with DirectWrite, you program with DWriteCore via its COM-light API, through the IDWriteFactory interface.

To use DWriteCore, it's necessary to include the dwrite_core.h header file.

// pch.h
...
// DWriteCore header file.
#include <dwrite_core.h>

The dwrite_core.h header file first defines the token DWRITE_CORE, and then it includes the dwrite_3.h header file. The DWRITE_CORE token is important, because it directs any subsequently included headers to make all of the DirectWrite APIs available to you. Once your project has included dwrite_core.h, you can then go ahead and write code, build, and run.

APIs that are new, or different, for DWriteCore

The DWriteCore API surface is the largely the same as it is for DirectWrite. But there are a small number of new APIs that are only in DWriteCore at the present.

Create a factory object

The DWriteCoreCreateFactory free function creates a factory object that is used for subsequent creation of individual DWriteCore objects.

DWriteCoreCreateFactory is functionally the same as the DWriteCreateFactory function exported by the system version of DirectWrite. The DWriteCore function has a different name to avoid ambiguity.

Create a restricted factory object

The DWRITE_FACTORY_TYPE enumeration has a new constant—DWRITE_FACTORY_TYPE_ISOLATED2, indicating a restricted factory. A restricted factory is more locked-down than an isolated factory. It doesn't interact with a cross-process nor persistent font cache in any way. In addition, the system font collection returned from this factory includes only well-known fonts. Here's how you can use DWRITE_FACTORY_TYPE_ISOLATED2 to create a restricted factory object when you call the DWriteCoreCreateFactory free function.

// Create a factory that doesn't interact with any cross-process nor
// persistent cache state.
winrt::com_ptr<::IDWriteFactory7> spFactory;
winrt::check_hresult(
  ::DWriteCoreCreateFactory(
    DWRITE_FACTORY_TYPE_ISOLATED2,
    __uuidof(spFactory),
    reinterpret_cast<IUnknown**>(spFactory.put())
  )
);

If you pass DWRITE_FACTORY_TYPE_ISOLATED2 to an older version of DirectWrite that doesn't support it, then DWriteCreateFactory returns E_INVALIDARG.

Drawing glyphs to a system memory bitmap

DirectWrite has a bitmap render target interface that supports rendering glyphs to a bitmap in system memory. However, currently the only way to get access to the underlying pixel data is to go through GDI, and so the API is not usable cross-platform. This is easily remedied by adding a method to retrieve the pixel data.

And so DWriteCore introduces the IDWriteBitmapRenderTarget2 interface, and its method IDWriteBitmapRenderTarget2::GetBitmapData. That method takes a parameter of (pointer to) type DWRITE_BITMAP_DATA_BGRA32, which is a new struct.

Your application creates a bitmap render target by calling IDWriteGdiInterop::CreateBitmapRenderTarget. On Windows, a bitmap render target encapsulates a GDI memory DC with a GDI device-independent bitmap (DIB) selected into it. IDWriteBitmapRenderTarget::DrawGlyphRun renders glyphs to the DIB. DirectWrite renders the glyphs itself without going through GDI. Your application can then get the HDC from the bitmap render target, and use BitBlt to copy the pixels to a window HDC.

On non-Windows platforms, your application can still create a bitmap render target, but it simply encapsulates a system memory array with no HDC and no DIB. Without an HDC, there needs to be another way for your application to get the bitmap pixels so that it can copy them, or otherwise use them. Even on Windows, it's sometimes useful to get the actual pixel data, and we show the current way to do so in the code example below.

// pch.h
#pragma once

#include <windows.h>
#include <Unknwn.h>
#include <winrt/Windows.Foundation.h>

// WinMain.cpp
#include "pch.h"
#include <dwrite_core.h>
#pragma comment(lib, "Gdi32")

class TextRenderer
{
    DWRITE_BITMAP_DATA_BGRA32 m_targetBitmapData;

public:
    void InitializeBitmapData(winrt::com_ptr<IDWriteBitmapRenderTarget> const& renderTarget)
    {
        // Query the bitmap render target for the new interface. 
        winrt::com_ptr<IDWriteBitmapRenderTarget2> renderTarget2;
        renderTarget2 = renderTarget.try_as<IDWriteBitmapRenderTarget2>();

        if (renderTarget2)
        {
            // IDWriteBitmapRenderTarget2 exists, so we can get the bitmap the easy way. 
            winrt::check_hresult(renderTarget2->GetBitmapData(OUT & m_targetBitmapData));
        }
        else
        {
            // We're using an older version that doesn't implement IDWriteBitmapRenderTarget2, 
            // so we have to get the bitmap by going through GDI. First get the bitmap handle. 
            HDC hdc = renderTarget->GetMemoryDC();
            winrt::handle dibHandle{ GetCurrentObject(hdc, OBJ_BITMAP) };
            winrt::check_bool(bool{ dibHandle });

            // Call a GDI function to fill in the DIBSECTION structure for the bitmap. 
            DIBSECTION dib;
            winrt::check_bool(GetObject(dibHandle.get(), sizeof(dib), &dib));

            m_targetBitmapData.width = dib.dsBm.bmWidth;
            m_targetBitmapData.height = dib.dsBm.bmHeight;
            m_targetBitmapData.pixels = static_cast<uint32_t*>(dib.dsBm.bmBits);
        }
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
    TextRenderer textRenderer;
    winrt::com_ptr<IDWriteBitmapRenderTarget> renderTarget{ /* ... */ };
    textRenderer.InitializeBitmapData(renderTarget);
}

Other API differences between DWriteCore and DirectWrite

There are a few APIs that are either stubs only, or they behave somewhat differently on non-Windows platforms. For example, IDWriteGdiInterop::CreateFontFaceFromHdc returns E_NOTIMPL on non-Windows platforms, since there's no such thing as an HDC without GDI.

And, finally, there are certain other Windows APIs that are typically used together with DirectWrite (Direct2D being a notable example). However, currently, Direct2D and DWriteCore don't interoperate. For example, if you create an IDWriteTextLayout using DWriteCore, and pass it to D2D1RenderTarget::DrawTextLayout, then that call will fail.