3

I'm trying to load a font from a file at runtime and display it with DirectWrite. The following code should initialize a IDWriteTextFormat object with that font:

hr = pDWriteFactory->CreateTextFormat(
            L"Font Family",    // Font family name
            NULL,              // Font collection (NULL sets it to use the system font collection)
                               // Somehow, a custom font collection should be used here, but I don't know how to do that
            DWRITE_FONT_WEIGHT_REGULAR,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            16.0f,
            L"en-us",
            &pTextFormat       // IDWriteTextFormat object
        );

It works perfectly with system fonts but I don't know how to load a custom font file. I would appreciate any examples on how to achieve that.

What I tried so far:
• I read this article and this question on Stackoverflow but I don't see where one should pass the file path of the font file (therefore, I completely failed to implement any of the code provided on those two pages)
• I also read this article but if I'm right, it has nothing to do with DirectWrite (?) (I tried to implement the AddFontResourceEx-method, but to no avail)
• Finally, the answer to this question suggested using ResourceFontContext::CreateFontCollection could solve the problem but after reading this page (it's in German but the screenshot is in English) I believe that it can only be used with fonts embedded as resources (which is not an option in my case)

Community
  • 1
  • 1
M_F
  • 396
  • 4
  • 11

2 Answers2

6

Sure, it's possible to do that. You'll need to:

  • implement IDWriteFontCollectionLoader interface in your code;

    You should also implement IDWriteFontFileEnumerator obviously, but this should be trivial.

  • register loader with the factory using RegisterFontCollectionLoader

  • create a collection using CreateCustomFontCollection and same factory;

  • pass it to CreateTextFormat called on same factory.

When you call CreateCustomFontCollection you have to provide a collection key and its size, it's a blob only meaningful to you, could be anything. Later on your loader will be called with this key, so you can identify it. For example you can use a string 'myspecialkey' as a key, and in CreateEnumeratorFromKey check if it's called with that key, rejecting any other key.

If you only wanted to create a fontface object from a path to a file, you don't need any of above, just use CreateFontFileReference followed by CreateFontFace.

Community
  • 1
  • 1
bunglehead
  • 1,104
  • 1
  • 14
  • 22
  • `IDWriteFontFace` doesn't seem to solve the problem (see [this question](http://stackoverflow.com/questions/11921592/how-to-create-idwritetextformat-from-idwritefontface)). Now to the four steps you suggested: I can create code that creates a custom font collection and compiles fine but I just don't know where to add my custom font. – M_F Jun 04 '16 at 15:13
  • You mean where to add a physical font file? It's up to you, it could be a separate file, embedded resource in your executable, anything really, as long as custom collection code you provide can make it accessible to directwrite system. – bunglehead Jun 04 '16 at 16:23
  • I believe that I was totally on the wrong track. I'm currently looking at [this page](https://msdn.microsoft.com/de-de/library/windows/desktop/dd941728(v=vs.85).aspx) and trying to adapt my code. I'll let you know if I succeeded in a few minutes. – M_F Jun 04 '16 at 16:47
  • It still doesn't work. However, I understand the basic principles of creating a custom font collection much better now. If neccessary, I will post a new question which is more specific after a little more trial and error. – M_F Jun 05 '16 at 10:42
  • @bunglehead I think the key is not only meaningful to the client, but also used internally, possibly to re-use cached versions of previously created font collections. (I passed `nullptr` and got a status access violation.) – phimuemue Dec 13 '17 at 09:22
  • 1
    @phimuemue sure, I'd say it's expected. What I meant is that key format is up to application, but as you discovered it should be readable to the specified length. NULL is probably special too and will crash regardless if anything was cached before or not. – bunglehead Dec 13 '17 at 10:39
6

If anyone is interested in the code which finally worked:
Add Common.h, FontLoader.h and FontLoader.cpp to your project (code is given below) and add the following lines to your application:

#include "FontLoader.h"
// ...
IDWriteFactory* pDWriteFactory;
IDWriteFontCollection *fCollection;
IDWriteTextFormat* pTextFormat;
// ...
MFFontContext fContext(pDWriteFactory);
std::vector<std::wstring> filePaths; // vector containing ABSOLUTE file paths of the font files which are to be added to the collection
std::wstring fontFileFilePath = L"C:\\xyz\\abc.ttf";
filePaths.push_back(fontFileFilePath);
HRESULT hr = fContext.CreateFontCollection(filePaths, &fCollection); // create custom font collection
hr = pDWriteFactory->CreateTextFormat(
            L"Font Family",    // Font family name
            fCollection,
            DWRITE_FONT_WEIGHT_REGULAR,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            16.0f,
            L"en-us",
            &pTextFormat       // IDWriteTextFormat object
        );

FontLoader.h

#pragma once
#include <string>
#include "Common.h"

typedef std::vector<std::wstring> MFCollection;

class MFFontCollectionLoader : public IDWriteFontCollectionLoader
{
public:
    MFFontCollectionLoader() : refCount_(0)
    {
    }

    // IUnknown methods
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    // IDWriteFontCollectionLoader methods
    virtual HRESULT STDMETHODCALLTYPE CreateEnumeratorFromKey(
        IDWriteFactory* factory,
        void const* collectionKey,                      // [collectionKeySize] in bytes
        UINT32 collectionKeySize,
        OUT IDWriteFontFileEnumerator** fontFileEnumerator
    );

    // Gets the singleton loader instance.
    static IDWriteFontCollectionLoader* GetLoader()
    {
        return instance_;
    }

    static bool IsLoaderInitialized()
    {
        return instance_ != NULL;
    }

private:
    ULONG refCount_;

    static IDWriteFontCollectionLoader* instance_;
};

class MFFontFileEnumerator : public IDWriteFontFileEnumerator
{
public:
    MFFontFileEnumerator(
        IDWriteFactory* factory
    );

    HRESULT Initialize(
        UINT const* collectionKey,    // [resourceCount]
        UINT32 keySize
    );

    ~MFFontFileEnumerator()
    {
        SafeRelease(&currentFile_);
        SafeRelease(&factory_);
    }

    // IUnknown methods
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    // IDWriteFontFileEnumerator methods
    virtual HRESULT STDMETHODCALLTYPE MoveNext(OUT BOOL* hasCurrentFile);
    virtual HRESULT STDMETHODCALLTYPE GetCurrentFontFile(OUT IDWriteFontFile** fontFile);

private:
    ULONG refCount_;

    IDWriteFactory* factory_;
    IDWriteFontFile* currentFile_;
    std::vector<std::wstring> filePaths_;
    size_t nextIndex_;
};

class MFFontContext
{
public:
    MFFontContext(IDWriteFactory *pFactory);
    ~MFFontContext();

    HRESULT Initialize();

    HRESULT CreateFontCollection(
        MFCollection &newCollection,
        OUT IDWriteFontCollection** result
    );

private:
    // Not copyable or assignable.
    MFFontContext(MFFontContext const&);
    void operator=(MFFontContext const&);

    HRESULT InitializeInternal();
    IDWriteFactory *g_dwriteFactory;
    static std::vector<unsigned int> cKeys;

    // Error code from Initialize().
    HRESULT hr_;
};

class MFFontGlobals
{
public:
    MFFontGlobals() {}
    static unsigned int push(MFCollection &addCollection)
    {
        unsigned int ret = fontCollections.size();
        fontCollections.push_back(addCollection);
        return ret;
    }
    static std::vector<MFCollection>& collections()
    {
        return fontCollections;
    }
private:
    static std::vector<MFCollection> fontCollections;
};

FontLoader.cpp

#include "FontLoader.h"

IDWriteFontCollectionLoader* MFFontCollectionLoader::instance_(
    new(std::nothrow) MFFontCollectionLoader()
);

HRESULT STDMETHODCALLTYPE MFFontCollectionLoader::QueryInterface(REFIID iid, void** ppvObject)
{
    if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontCollectionLoader))
    {
        *ppvObject = this;
        AddRef();
        return S_OK;
    }
    else
    {
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
}

ULONG STDMETHODCALLTYPE MFFontCollectionLoader::AddRef()
{
    return InterlockedIncrement(&refCount_);
}

ULONG STDMETHODCALLTYPE MFFontCollectionLoader::Release()
{
    ULONG newCount = InterlockedDecrement(&refCount_);
    if (newCount == 0)
        delete this;

    return newCount;
}

HRESULT STDMETHODCALLTYPE MFFontCollectionLoader::CreateEnumeratorFromKey(
    IDWriteFactory* factory,
    void const* collectionKey,                      // [collectionKeySize] in bytes
    UINT32 collectionKeySize,
    OUT IDWriteFontFileEnumerator** fontFileEnumerator
)
{
    *fontFileEnumerator = NULL;

    HRESULT hr = S_OK;

    if (collectionKeySize % sizeof(UINT) != 0)
        return E_INVALIDARG;

    MFFontFileEnumerator* enumerator = new(std::nothrow) MFFontFileEnumerator(
        factory
    );
    if (enumerator == NULL)
        return E_OUTOFMEMORY;

    UINT const* mfCollectionKey = static_cast<UINT const*>(collectionKey);
    UINT32 const mfKeySize = collectionKeySize;

    hr = enumerator->Initialize(
        mfCollectionKey,
        mfKeySize
    );

    if (FAILED(hr))
    {
        delete enumerator;
        return hr;
    }

    *fontFileEnumerator = SafeAcquire(enumerator);

    return hr;
}

// ------------------------------ MFFontFileEnumerator ----------------------------------------------------------

MFFontFileEnumerator::MFFontFileEnumerator(
    IDWriteFactory* factory
) :
    refCount_(0),
    factory_(SafeAcquire(factory)),
    currentFile_(),
    nextIndex_(0)
{
}

HRESULT MFFontFileEnumerator::Initialize(
    UINT const* collectionKey,    // [resourceCount]
    UINT32 keySize
)
{
    try
    {
        // dereference collectionKey in order to get index of collection in MFFontGlobals::fontCollections vector
        UINT cPos = *collectionKey;
        for (MFCollection::iterator it = MFFontGlobals::collections()[cPos].begin(); it != MFFontGlobals::collections()[cPos].end(); ++it)
        {
            filePaths_.push_back(it->c_str());
        }
    }
    catch (...)
    {
        return ExceptionToHResult();
    }
    return S_OK;
}

HRESULT STDMETHODCALLTYPE MFFontFileEnumerator::QueryInterface(REFIID iid, void** ppvObject)
{
    if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileEnumerator))
    {
        *ppvObject = this;
        AddRef();
        return S_OK;
    }
    else
    {
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
}

ULONG STDMETHODCALLTYPE MFFontFileEnumerator::AddRef()
{
    return InterlockedIncrement(&refCount_);
}

ULONG STDMETHODCALLTYPE MFFontFileEnumerator::Release()
{
    ULONG newCount = InterlockedDecrement(&refCount_);
    if (newCount == 0)
        delete this;

    return newCount;
}

HRESULT STDMETHODCALLTYPE MFFontFileEnumerator::MoveNext(OUT BOOL* hasCurrentFile)
{
    HRESULT hr = S_OK;

    *hasCurrentFile = FALSE;
    SafeRelease(&currentFile_);

    if (nextIndex_ < filePaths_.size())
    {
        hr = factory_->CreateFontFileReference(
            filePaths_[nextIndex_].c_str(),
            NULL,
            &currentFile_
        );

        if (SUCCEEDED(hr))
        {
            *hasCurrentFile = TRUE;

            ++nextIndex_;
        }
    }

    return hr;
}

HRESULT STDMETHODCALLTYPE MFFontFileEnumerator::GetCurrentFontFile(OUT IDWriteFontFile** fontFile)
{
    *fontFile = SafeAcquire(currentFile_);

    return (currentFile_ != NULL) ? S_OK : E_FAIL;
}

// ---------------------------------------- MFFontContext ---------------------------------------------------------

MFFontContext::MFFontContext(IDWriteFactory *pFactory) : hr_(S_FALSE), g_dwriteFactory(pFactory)
{
}

MFFontContext::~MFFontContext()
{
    g_dwriteFactory->UnregisterFontCollectionLoader(MFFontCollectionLoader::GetLoader());
}

HRESULT MFFontContext::Initialize()
{
    if (hr_ == S_FALSE)
    {
        hr_ = InitializeInternal();
    }
    return hr_;
}

HRESULT MFFontContext::InitializeInternal()
{
    HRESULT hr = S_OK;

    if (!MFFontCollectionLoader::IsLoaderInitialized())
    {
        return E_FAIL;
    }

    // Register our custom loader with the factory object.
    hr = g_dwriteFactory->RegisterFontCollectionLoader(MFFontCollectionLoader::GetLoader());

    return hr;
}

HRESULT MFFontContext::CreateFontCollection(
    MFCollection &newCollection,
    OUT IDWriteFontCollection** result
)
{
    *result = NULL;

    HRESULT hr = S_OK;

    // save new collection in MFFontGlobals::fontCollections vector
    UINT collectionKey = MFFontGlobals::push(newCollection);
    cKeys.push_back(collectionKey);
    const void *fontCollectionKey = &cKeys.back();
    UINT32 keySize = sizeof(collectionKey);

    hr = Initialize();
    if (FAILED(hr))
        return hr;

    hr = g_dwriteFactory->CreateCustomFontCollection(
        MFFontCollectionLoader::GetLoader(),
        fontCollectionKey,
        keySize,
        result
    );

    return hr;
}

std::vector<unsigned int> MFFontContext::cKeys = std::vector<unsigned int>(0);

// ----------------------------------- MFFontGlobals ---------------------------------------------------------

std::vector<MFCollection> MFFontGlobals::fontCollections = std::vector<MFCollection>(0);

Common.h

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
//----------------------------------------------------------------------------

#pragma once

// The following macros define the minimum required platform.  The minimum required platform
// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run 
// your application.  The macros work by enabling all features available on platform versions up to and 
// including the version specified.

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.

#ifndef WINVER                  // Minimum platform is Windows 7
#define WINVER 0x0601
#endif

#ifndef _WIN32_WINNT            // Minimum platform is Windows 7
#define _WIN32_WINNT 0x0601
#endif

#ifndef _WIN32_WINDOWS          // Minimum platform is Windows 7
#define _WIN32_WINDOWS 0x0601
#endif

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX

#ifndef UNICODE
#define UNICODE
#endif

// Windows header files
#include <windows.h>
#include <dwrite.h>
#include <d2d1.h>

// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <memory>
#include <vector>

// Ignore unreferenced parameters, since they are very common
// when implementing callbacks.
#pragma warning(disable : 4100)


////////////////////////////////////////
// COM inheritance helpers.

// Releases a COM object and nullifies pointer.
template <typename InterfaceType>
inline void SafeRelease(InterfaceType** currentObject)
{
    if (*currentObject != NULL)
    {
        (*currentObject)->Release();
        *currentObject = NULL;
    }
}


// Acquires an additional reference, if non-null.
template <typename InterfaceType>
inline InterfaceType* SafeAcquire(InterfaceType* newObject)
{
    if (newObject != NULL)
        newObject->AddRef();

    return newObject;
}


// Sets a new COM object, releasing the old one.
template <typename InterfaceType>
inline void SafeSet(InterfaceType** currentObject, InterfaceType* newObject)
{
    SafeAcquire(newObject);
    SafeRelease(&currentObject);
    currentObject = newObject;
}


// Maps exceptions to equivalent HRESULTs,
inline HRESULT ExceptionToHResult() throw()
{
    try
    {
        throw;  // Rethrow previous exception.
    }
    catch (std::bad_alloc&)
    {
        return E_OUTOFMEMORY;
    }
    catch (...)
    {
        return E_FAIL;
    }
}

The code is certainly not perfect but it worked for me.
If you have any questions concerning this code just leave a comment (even though I probably won't be able to give a satisfying answer anymore). Note that I copied large parts of the code from here.

M_F
  • 396
  • 4
  • 11
  • 1
    Tis a little easier in Windows 10 Fall SDK 16232: dwriteFactory->CreateFontSetBuilder(OUT &fontSetBuilder2); fontSetBuilder2->AddFontFile(fullFontFilePath.c_str()); fontSetBuilder2->CreateFontSet(OUT &fontSet); dwriteFactory3->CreateFontCollectionFromFontSet(fontSet, OUT &fontCollection); – Dwayne Robinson Sep 08 '17 at 07:25
  • Yes but the problem with this approach is that `CreateFontCollectionFromFontSet` creates a `IDWriteFontCollection1`, and `CreateTextFormat` takes an `IDWriteFontCollection`. those two cannot be reconciled, so we still have to take the `CreateCustomFontCollection` route. – Cee McSharpface Dec 05 '19 at 20:37
  • I am using this code and it does work pretty well. However, very sporadic it crashes in MFFontFileEnumerator::MoveNext(). The line SafeRelease(&currentFile_); throws the exception 0xC0000005: Access violation. I can't figure out what causes this, maybe you have an idea? – StefanFFM Jan 07 '20 at 17:27
  • @Stefan I fear I cannot really help you there. I can hardly remember anymore what my code is doing, and on top of that, I copied large parts of it (in particular the line where the exception is thrown) from [here](https://learn.microsoft.com/de-de/previous-versions/technical-content/dd941728(v=vs.85)?redirectedfrom=MSDN). The only thing I can tell you is that SafeRelease is actually defined in Common.h - if you didn't already notice yourself. If you remove the inline keyword, you might get more details... even though I can't imagine that will get you very far - sorry. – M_F Jan 08 '20 at 19:09