C++ - DirectWrite:在运行时从文件加载字体

C++ - DirectWrite: Load font from file at runtime

我正在尝试在运行时从文件加载字体并使用 DirectWrite 显示它。以下代码应使用该字体初始化 IDWriteTextFormat 对象:

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
        );

它与系统字体完美配合,但我不知道如何加载自定义字体文件。我将不胜感激有关如何实现这一目标的任何示例。

到目前为止我尝试了什么:
• 我阅读了 this article and this question on Whosebug 但我看不到应该将字体文件的文件路径传递到哪里(因此,我完全没有实现这两页上提供的任何代码)
• 我也读过 this article 但如果我是对的,它与 DirectWrite 无关(?)(我试图实现 AddFontResourceEx 方法,但无济于事)
• 最后, suggested using ResourceFontContext::CreateFontCollection could solve the problem but after reading this page 的答案(它是德文的,但屏幕截图是英文的)我认为它只能与作为资源嵌入的字体一起使用(在我的情况下这不是一个选项)

当然可以。您需要:

  • 在您的代码中实现 IDWriteFontCollectionLoader 接口;

    显然,您还应该实现 IDWriteFontFileEnumerator,但这应该是微不足道的。

  • 使用 RegisterFontCollectionLoader

    向工厂注册加载器
  • 使用 CreateCustomFontCollection 和同一工厂创建一个集合;

  • 将其传递给在同一工厂调用的 CreateTextFormat

当您调用 CreateCustomFontCollection 时,您必须提供一个集合键及其大小,它是一个只对您有意义的 blob,可以是任何东西。稍后将使用此密钥调用您的加载程序,因此您可以识别它。例如,您可以使用字符串 'myspecialkey' 作为键,并在 CreateEnumeratorFromKey 中检查它是否使用该键调用,拒绝任何其他键。

如果您只想从文件路径创建字体对象,则不需要以上任何一项,只需使用 CreateFontFileReference 后跟 CreateFontFace

如果有人对最终运行的代码感兴趣:
Common.hFontLoader.hFontLoader.cpp 添加到您的项目(代码如下),并将以下行添加到您的应用程序:

#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;
    }
}

代码当然不完美,但对我有用。
如果您对这段代码有任何疑问,请发表评论(尽管我可能无法再给出令人满意的答案)。请注意,我从 here.

复制了大部分代码